Merge "Rename Do Not Disturb -> Priority Mode"
diff --git a/Android.bp b/Android.bp
index cc754f2..8d82b38 100644
--- a/Android.bp
+++ b/Android.bp
@@ -136,6 +136,7 @@
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
         ":libupdate_engine_aidl",
+        ":logd_aidl",
         ":resourcemanager_aidl",
         ":storaged_aidl",
         ":vold_aidl",
@@ -538,7 +539,9 @@
     visibility: ["//visibility:private"],
 }
 
-// These defaults are used for both the jar stubs and the doc stubs.
+// Defaults for all stubs that include the non-updatable framework. These defaults do not include
+// module symbols, so will not compile correctly on their own. Users must add module APIs to the
+// classpath (or sources) somehow.
 stubs_defaults {
     name: "android-non-updatable-stubs-defaults",
     srcs: [":android-non-updatable-stub-sources"],
@@ -546,17 +549,14 @@
     system_modules: "none",
     java_version: "1.8",
     arg_files: ["core/res/AndroidManifest.xml"],
-    // TODO(b/147699819): remove below aidl includes.
     aidl: {
         local_include_dirs: [
-            "apex/media/aidl/stable",
             "media/aidl",
             "telephony/java",
         ],
         include_dirs: [
             "frameworks/av/aidl",
             "frameworks/native/libs/permission/aidl",
-            "packages/modules/Connectivity/framework/aidl-export",
         ],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
@@ -576,6 +576,30 @@
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.vibrator-V1.3-java",
         "framework-protos",
+    ],
+    filter_packages: packages_to_document,
+    high_mem: true, // Lots of sources => high memory use, see b/170701554
+    installable: false,
+    annotations_enabled: true,
+    previous_api: ":android.api.public.latest",
+    merge_annotations_dirs: ["metalava-manual"],
+    defaults_visibility: ["//visibility:private"],
+    visibility: ["//frameworks/base/api"],
+}
+
+// Defaults with module APIs in the classpath (mostly from prebuilts).
+// Suitable for compiling android-non-updatable.
+stubs_defaults {
+    name: "module-classpath-stubs-defaults",
+    aidl: {
+        local_include_dirs: [
+            "apex/media/aidl/stable",
+        ],
+        include_dirs: [
+            "packages/modules/Connectivity/framework/aidl-export",
+        ],
+    },
+    libs: [
         "art.module.public.api",
         "sdk_module-lib_current_framework-tethering",
         // There are a few classes from modules used by the core that
@@ -586,14 +610,7 @@
         // NOTE: The below can be removed once the prebuilt stub contains IKE.
         "sdk_system_current_android.net.ipsec.ike",
     ],
-    filter_packages: packages_to_document,
-    high_mem: true, // Lots of sources => high memory use, see b/170701554
-    installable: false,
-    annotations_enabled: true,
-    previous_api: ":android.api.public.latest",
-    merge_annotations_dirs: ["metalava-manual"],
     defaults_visibility: ["//visibility:private"],
-    visibility: ["//frameworks/base/api"],
 }
 
 build = [
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 4aecc8f..3d6b52f 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -57,7 +57,10 @@
 
 stubs_defaults {
     name: "android-non-updatable-doc-stubs-defaults",
-    defaults: ["android-non-updatable-stubs-defaults"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     srcs: [
         // No longer part of the stubs, but are included in the docs.
         ":android-test-base-sources",
@@ -71,39 +74,21 @@
 
 stubs_defaults {
     name: "framework-doc-stubs-default",
+    defaults: ["android-non-updatable-stubs-defaults"],
     srcs: [
-        ":android-non-updatable-stub-sources",
-
         // No longer part of the stubs, but are included in the docs.
         ":android-test-base-sources",
         ":android-test-mock-sources",
         ":android-test-runner-sources",
     ],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
     libs: framework_docs_only_libs,
     create_doc_stubs: true,
-    annotations_enabled: true,
-    filter_packages: packages_to_document,
     api_levels_annotations_enabled: true,
     api_levels_annotations_dirs: [
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    previous_api: ":android.api.public.latest",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
     write_sdk_values: true,
-    // TODO(b/169090544): remove below aidl includes.
-    aidl: {
-        local_include_dirs: ["media/aidl"],
-        include_dirs: [
-            "frameworks/av/aidl",
-            "frameworks/native/libs/permission/aidl",
-        ],
-    },
 }
 
 // Defaults module for doc-stubs targets that use module source code as input.
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 131d5e2..8a15758 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -37,7 +37,10 @@
 
 droidstubs {
     name: "api-stubs-docs-non-updatable",
-    defaults: ["android-non-updatable-stubs-defaults"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args,
     check_api: {
         current: {
@@ -86,7 +89,10 @@
 
 droidstubs {
     name: "system-api-stubs-docs-non-updatable",
-    defaults: ["android-non-updatable-stubs-defaults"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args + priv_apps,
     check_api: {
         current: {
@@ -122,7 +128,10 @@
 
 droidstubs {
     name: "test-api-stubs-docs-non-updatable",
-    defaults: ["android-non-updatable-stubs-defaults"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args + test + priv_apps_in_stubs,
     check_api: {
         current: {
@@ -164,7 +173,10 @@
 
 droidstubs {
     name: "module-lib-api-stubs-docs-non-updatable",
-    defaults: ["android-non-updatable-stubs-defaults"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
     check_api: {
         current: {
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index e873514..67a3380 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -218,7 +218,7 @@
             })
 
         override fun parseImpl(file: File) =
-                parser.parsePackage(input.get()!!.reset(), file, 0).result
+                parser.parsePackage(input.get()!!.reset(), file, 0, null).result
                         as ParsingPackageImpl
     }
 
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index 1be68f5..4ad015d 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -20,7 +20,6 @@
 
 import android.app.Activity;
 import android.content.Context;
-import android.graphics.Point;
 import android.os.RemoteException;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
@@ -133,12 +132,10 @@
         final WindowManager.LayoutParams mParams;
         final int mWidth;
         final int mHeight;
-        final Point mOutSurfaceSize = new Point();
         final SurfaceControl mOutSurfaceControl;
 
         final IntSupplier mViewVisibility;
 
-        int mFrameNumber;
         int mFlags;
 
         RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -155,9 +152,8 @@
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
                 session.relayout(mWindow, mParams, mWidth, mHeight,
-                        mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
-                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
-                        mOutSurfaceSize);
+                        mViewVisibility.getAsInt(), mFlags, mOutFrames,
+                        mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls);
             }
         }
     }
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 12a8654..d93ad3c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1633,12 +1633,9 @@
             for (int i=0; i<mActiveServices.size(); i++) {
                 final JobServiceContext jsc = mActiveServices.get(i);
                 final JobStatus job = jsc.getRunningJobLocked();
-                if (job != null
-                        && !job.canRunInDoze()
-                        && !job.dozeWhitelisted
-                        && !job.uidActive) {
-                    // We will report active if we have a job running and it is not an exception
-                    // due to being in the foreground or whitelisted.
+                if (job != null && !job.canRunInDoze()) {
+                    // We will report active if we have a job running and it does not have an
+                    // exception that allows it to run in Doze.
                     active = true;
                     break;
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 090260c..f6de109 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -246,7 +246,7 @@
             pw.print((jobStatus.satisfiedConstraints
                     & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
                             ? " RUNNABLE" : " WAITING");
-            if (jobStatus.dozeWhitelisted) {
+            if (jobStatus.appHasDozeExemption) {
                 pw.print(" WHITELISTED");
             }
             if (mAllowInIdleJobs.contains(jobStatus)) {
@@ -273,7 +273,7 @@
             proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
             proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
                     (jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
-            proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
+            proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.appHasDozeExemption);
             proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
 
             proto.end(jsToken);
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 f74a4fa..0eea701 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
@@ -267,7 +267,7 @@
     private final boolean mHasMediaBackupExemption;
 
     // Set to true if doze constraint was satisfied due to app being whitelisted.
-    public boolean dozeWhitelisted;
+    boolean appHasDozeExemption;
 
     // Set to true when the app is "active" per AppStateTracker
     public boolean uidActive;
@@ -1179,7 +1179,8 @@
      * in Doze.
      */
     public boolean canRunInDoze() {
-        return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+        return appHasDozeExemption
+                || (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
                 || ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
                 && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
     }
@@ -1243,7 +1244,7 @@
     /** @return true if the constraint was changed, false otherwise. */
     boolean setDeviceNotDozingConstraintSatisfied(final long nowElapsed,
             boolean state, boolean whitelisted) {
-        dozeWhitelisted = whitelisted;
+        appHasDozeExemption = whitelisted;
         if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, nowElapsed, state)) {
             // The constraint was changed. Update the ready flag.
             mReadyNotDozing = state || canRunInDoze();
@@ -2110,7 +2111,7 @@
             }
             pw.decreaseIndent();
 
-            if (dozeWhitelisted) {
+            if (appHasDozeExemption) {
                 pw.println("Doze whitelisted: true");
             }
             if (uidActive) {
@@ -2323,7 +2324,7 @@
             dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
             dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
                     ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
-            proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
+            proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, appHasDozeExemption);
             proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive);
             proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY,
                     job.isExemptedFromAppStandby());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 65e1d49..dd5246a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.IUidObserver;
+import android.app.job.JobInfo;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -161,6 +162,28 @@
         public long inQuotaTimeElapsed;
 
         /**
+         * The time after which the app will be under the bucket quota and can start running
+         * low priority jobs again. This is only valid if
+         * {@link #executionTimeInWindowMs} >=
+         *        {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityLow}),
+         * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+         * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+         * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+         */
+        public long inQuotaTimeLowElapsed;
+
+        /**
+         * The time after which the app will be under the bucket quota and can start running
+         * min priority jobs again. This is only valid if
+         * {@link #executionTimeInWindowMs} >=
+         *        {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityMin}),
+         * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+         * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+         * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+         */
+        public long inQuotaTimeMinElapsed;
+
+        /**
          * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
          * in the elapsed realtime timebase.
          */
@@ -199,6 +222,8 @@
                     + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
                     + "sessionCountInWindow=" + sessionCountInWindow + ", "
                     + "inQuotaTime=" + inQuotaTimeElapsed + ", "
+                    + "inQuotaTimeLow=" + inQuotaTimeLowElapsed + ", "
+                    + "inQuotaTimeMin=" + inQuotaTimeMinElapsed + ", "
                     + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
                     + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", "
                     + "rateLimitSessionCountExpirationTime="
@@ -351,6 +376,24 @@
      */
     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
 
+    /**
+     * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by
+     * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
+     * surplus of this amount of remaining allowed time before we start running low priority
+     * jobs.
+     */
+    private float mAllowedTimeSurplusPriorityLow =
+            QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
+
+    /**
+     * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by
+     * {@link JobInfo#PRIORITY_MIN min priority} jobs. In other words, there must be a minimum
+     * surplus of this amount of remaining allowed time before we start running low priority
+     * jobs.
+     */
+    private float mAllowedTimeSurplusPriorityMin =
+            QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
     /** The period of time used to rate limit recently run jobs. */
     private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
 
@@ -653,10 +696,11 @@
             boolean forUpdate) {
         if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
             unprepareFromExecutionLocked(jobStatus);
-            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
-                    jobStatus.getSourcePackageName());
-            if (jobs != null) {
-                jobs.remove(jobStatus);
+            final int userId = jobStatus.getSourceUserId();
+            final String pkgName = jobStatus.getSourcePackageName();
+            ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+            if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
+                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
             }
         }
     }
@@ -771,7 +815,8 @@
                 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
             }
             return getTimeUntilQuotaConsumedLocked(
-                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
+                    jobStatus.getEffectivePriority());
         }
 
         // Expedited job.
@@ -856,7 +901,8 @@
         return isTopStartedJobLocked(jobStatus)
                 || isUidInForeground(jobStatus.getSourceUid())
                 || isWithinQuotaLocked(
-                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket,
+                jobStatus.getEffectivePriority());
     }
 
     @GuardedBy("mLock")
@@ -873,7 +919,7 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
-            final int standbyBucket) {
+            final int standbyBucket, final int priority) {
         if (!mIsEnabled) {
             return true;
         }
@@ -881,9 +927,16 @@
 
         if (isQuotaFreeLocked(standbyBucket)) return true;
 
+        final long minSurplus;
+        if (priority <= JobInfo.PRIORITY_MIN) {
+            minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin);
+        } else if (priority <= JobInfo.PRIORITY_LOW) {
+            minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow);
+        } else {
+            minSurplus = 0;
+        }
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
-        // TODO: use a higher minimum remaining time for jobs with MINIMUM priority
-        return getRemainingExecutionTimeLocked(stats) > 0
+        return getRemainingExecutionTimeLocked(stats) > minSurplus
                 && isUnderJobCountQuotaLocked(stats, standbyBucket)
                 && isUnderSessionCountQuotaLocked(stats, standbyBucket);
     }
@@ -1001,7 +1054,8 @@
      * job is running.
      */
     @VisibleForTesting
-    long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
+    long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName,
+            @JobInfo.Priority int jobPriority) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
         final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
                 packageName, userId, nowElapsed);
@@ -1022,10 +1076,15 @@
 
         final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
         final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
-        final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
+        final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority);
+        final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
         final long maxExecutionTimeRemainingMs =
                 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
 
+        if (allowedTimeRemainingMs <= 0 || maxExecutionTimeRemainingMs <= 0) {
+            return 0;
+        }
+
         // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
         // essentially run until they reach the maximum limit.
         if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
@@ -1044,6 +1103,16 @@
                         sessions, startWindowElapsed, allowedTimeRemainingMs));
     }
 
+    private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) {
+        if (jobPriority <= JobInfo.PRIORITY_MIN) {
+            return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin));
+        }
+        if (jobPriority <= JobInfo.PRIORITY_LOW) {
+            return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow));
+        }
+        return mAllowedTimePerPeriodMs;
+    }
+
     /**
      * Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
      *
@@ -1198,10 +1267,13 @@
         stats.sessionCountInWindow = 0;
         if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) {
             // App won't be in quota until configuration changes.
-            stats.inQuotaTimeElapsed = Long.MAX_VALUE;
+            stats.inQuotaTimeElapsed = stats.inQuotaTimeLowElapsed = stats.inQuotaTimeMinElapsed =
+                    Long.MAX_VALUE;
         } else {
             stats.inQuotaTimeElapsed = 0;
         }
+        final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN);
+        final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW);
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -1219,13 +1291,25 @@
                 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
                         nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
             }
+            if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+                stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+                        nowElapsed - allowedTimeLowMs + stats.windowSizeMs);
+            }
+            if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+                stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+                        nowElapsed - allowedTimeMinMs + stats.windowSizeMs);
+            }
             if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                        nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
+                final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS;
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+                stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+                stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
             }
             if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
-                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                        nowElapsed + stats.windowSizeMs);
+                final long inQuotaTime = nowElapsed + stats.windowSizeMs;
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+                stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+                stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
             }
         }
 
@@ -1267,9 +1351,23 @@
                             start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
                                     + stats.windowSizeMs);
                 }
+                if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+                    stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+                            start + stats.executionTimeInWindowMs - allowedTimeLowMs
+                                    + stats.windowSizeMs);
+                }
+                if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+                    stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+                            start + stats.executionTimeInWindowMs - allowedTimeMinMs
+                                    + stats.windowSizeMs);
+                }
                 if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
-                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            session.endTimeElapsed + stats.windowSizeMs);
+                    final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+                    stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+                            inQuotaTime);
+                    stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+                            inQuotaTime);
                 }
                 if (i == loopStart
                         || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
@@ -1278,8 +1376,12 @@
                     sessionCountInWindow++;
 
                     if (sessionCountInWindow >= stats.sessionCountLimit) {
-                        stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                                session.endTimeElapsed + stats.windowSizeMs);
+                        final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+                        stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+                        stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+                                inQuotaTime);
+                        stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+                                inQuotaTime);
                     }
                 }
             }
@@ -1425,10 +1527,9 @@
         synchronized (mLock) {
             final long nowElapsed = sElapsedRealtimeClock.millis();
             final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
-            if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)
-                    && maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
-                mStateChangedListener
-                        .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
+            if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) {
+                mStateChangedListener.onControllerStateChanged(
+                        maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
             }
         }
     }
@@ -1558,9 +1659,8 @@
             final int userId = mTrackedJobs.keyAt(u);
             for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
                 final String packageName = mTrackedJobs.keyAt(u, p);
-                if (maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
-                    changedJobs.addAll(mTrackedJobs.valueAt(u, p));
-                }
+                changedJobs.addAll(
+                        maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
             }
         }
         if (changedJobs.size() > 0) {
@@ -1573,18 +1673,20 @@
      *
      * @return true if at least one job had its bit changed
      */
-    private boolean maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId,
-            @NonNull final String packageName) {
+    @NonNull
+    private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed,
+            final int userId, @NonNull final String packageName) {
         ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+        final ArraySet<JobStatus> changedJobs = new ArraySet<>();
         if (jobs == null || jobs.size() == 0) {
-            return false;
+            return changedJobs;
         }
 
         // Quota is the same for all jobs within a package.
         final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
-        final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
+        final boolean realInQuota = isWithinQuotaLocked(
+                userId, packageName, realStandbyBucket, JobInfo.PRIORITY_DEFAULT);
         boolean outOfEJQuota = false;
-        boolean changed = false;
         for (int i = jobs.size() - 1; i >= 0; --i) {
             final JobStatus js = jobs.valueAt(i);
             final boolean isWithinEJQuota =
@@ -1592,21 +1694,30 @@
             if (isTopStartedJobLocked(js)) {
                 // Job was started while the app was in the TOP state so we should allow it to
                 // finish.
-                changed |= js.setQuotaConstraintSatisfied(nowElapsed, true);
+                if (js.setQuotaConstraintSatisfied(nowElapsed, true)) {
+                    changedJobs.add(js);
+                }
             } else if (realStandbyBucket != ACTIVE_INDEX
-                    && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+                    && realStandbyBucket == js.getEffectiveStandbyBucket()
+                    && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
                 // An app in the ACTIVE bucket may be out of quota while the job could be in quota
                 // for some reason. Therefore, avoid setting the real value here and check each job
                 // individually.
-                changed |= setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota);
+                if (setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota)) {
+                    changedJobs.add(js);
+                }
             } else {
                 // This job is somehow exempted. Need to determine its own quota status.
-                changed |= setConstraintSatisfied(js, nowElapsed,
-                        isWithinEJQuota || isWithinQuotaLocked(js));
+                if (setConstraintSatisfied(js, nowElapsed,
+                        isWithinEJQuota || isWithinQuotaLocked(js))) {
+                    changedJobs.add(js);
+                }
             }
 
             if (js.isRequestedExpeditedJob()) {
-                changed |= setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota);
+                if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) {
+                    changedJobs.add(js);
+                }
                 outOfEJQuota |= !isWithinEJQuota;
             }
         }
@@ -1618,7 +1729,7 @@
         } else {
             mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
         }
-        return changed;
+        return changedJobs;
     }
 
     private class UidConstraintUpdater implements Consumer<JobStatus> {
@@ -1651,9 +1762,9 @@
             final int userId = jobStatus.getSourceUserId();
             final String packageName = jobStatus.getSourcePackageName();
             final int realStandbyBucket = jobStatus.getStandbyBucket();
-            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
-                // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
-                // that all jobs for the userId-package are within quota.
+            if (isWithinEJQuota
+                    && isWithinQuotaLocked(userId, packageName, realStandbyBucket,
+                    JobInfo.PRIORITY_MIN)) {
                 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
             } else {
                 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
@@ -1700,16 +1811,41 @@
             return;
         }
 
+        ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+        if (jobs == null || jobs.size() == 0) {
+            Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+                    + packageToString(userId, packageName) + " that has no jobs");
+            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            return;
+        }
+
         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
         final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
         final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
                 standbyBucket);
         final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
 
-        final boolean inRegularQuota = stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
-                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
-                && isUnderJobCountQuota
-                && isUnderTimingSessionCountQuota;
+        int minPriority = JobInfo.PRIORITY_MAX;
+        boolean hasDefPlus = false, hasLow = false, hasMin = false;
+        for (int i = jobs.size() - 1; i >= 0; --i) {
+            final int priority = jobs.valueAt(i).getEffectivePriority();
+            minPriority = Math.min(minPriority, priority);
+            if (priority <= JobInfo.PRIORITY_MIN) {
+                hasMin = true;
+            } else if (priority <= JobInfo.PRIORITY_LOW) {
+                hasLow = true;
+            } else {
+                hasDefPlus = true;
+            }
+            if (hasMin && hasLow && hasDefPlus) {
+                break;
+            }
+        }
+        final boolean inRegularQuota =
+                stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+                        && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+                        && isUnderJobCountQuota
+                        && isUnderTimingSessionCountQuota;
         if (inRegularQuota && remainingEJQuota > 0) {
             // Already in quota. Why was this method called?
             if (DEBUG) {
@@ -1728,7 +1864,24 @@
         long inEJQuotaTimeElapsed = Long.MAX_VALUE;
         if (!inRegularQuota) {
             // The time this app will have quota again.
-            long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+            long executionInQuotaTime = Long.MAX_VALUE;
+            boolean hasExecutionInQuotaTime = false;
+            if (hasMin && stats.inQuotaTimeMinElapsed > 0) {
+                executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeMinElapsed);
+                hasExecutionInQuotaTime = true;
+            }
+            if (hasLow && stats.inQuotaTimeLowElapsed > 0) {
+                executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeLowElapsed);
+                hasExecutionInQuotaTime = true;
+            }
+            if (hasDefPlus && stats.inQuotaTimeElapsed > 0) {
+                executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeElapsed);
+                hasExecutionInQuotaTime = true;
+            }
+            long inQuotaTimeElapsed = 0;
+            if (hasExecutionInQuotaTime) {
+                inQuotaTimeElapsed = executionInQuotaTime;
+            }
             if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
                 // App hit the rate limit.
                 inQuotaTimeElapsed =
@@ -1941,6 +2094,7 @@
         private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
         private long mStartTimeElapsed;
         private int mBgJobCount;
+        private int mLowestPriority = JobInfo.PRIORITY_MAX;
         private long mDebitAdjustment;
 
         Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
@@ -1963,6 +2117,7 @@
                 Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
             }
             // Always maintain list of running jobs, even when quota is free.
+            mLowestPriority = Math.min(mLowestPriority, jobStatus.getEffectivePriority());
             if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) {
                 mBgJobCount++;
                 if (mRegularJobTimer) {
@@ -2002,6 +2157,13 @@
                         && !isQuotaFreeLocked(standbyBucket)) {
                     emitSessionLocked(nowElapsed);
                     cancelCutoff();
+                    mLowestPriority = JobInfo.PRIORITY_MAX;
+                } else if (mLowestPriority == jobStatus.getEffectivePriority()) {
+                    mLowestPriority = JobInfo.PRIORITY_MAX;
+                    for (int i = mRunningBgJobs.size() - 1; i >= 0; --i) {
+                        mLowestPriority = Math.min(mLowestPriority,
+                                mRunningBgJobs.valueAt(i).getEffectivePriority());
+                    }
                 }
             }
         }
@@ -2128,9 +2290,14 @@
                 }
                 Message msg = mHandler.obtainMessage(
                         mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
-                final long timeRemainingMs = mRegularJobTimer
-                        ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
-                        : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+                final long timeRemainingMs;
+                if (mRegularJobTimer) {
+                    timeRemainingMs = getTimeUntilQuotaConsumedLocked(
+                            mPkg.userId, mPkg.packageName, mLowestPriority);
+                } else {
+                    timeRemainingMs =
+                            getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+                }
                 if (DEBUG) {
                     Slog.i(TAG,
                             (mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has "
@@ -2250,11 +2417,10 @@
                         final ShrinkableDebits debits =
                                 getEJDebitsLocked(mPkg.userId, mPkg.packageName);
                         if (transactQuotaLocked(mPkg.userId, mPkg.packageName,
-                                nowElapsed, debits, pendingReward)
-                                && maybeUpdateConstraintForPkgLocked(nowElapsed,
-                                mPkg.userId, mPkg.packageName)) {
+                                nowElapsed, debits, pendingReward)) {
                             mStateChangedListener.onControllerStateChanged(
-                                    mTrackedJobs.get(mPkg.userId, mPkg.packageName));
+                                    maybeUpdateConstraintForPkgLocked(nowElapsed,
+                                            mPkg.userId, mPkg.packageName));
                         }
                     }
                     break;
@@ -2356,11 +2522,9 @@
             if (timer != null && timer.isActive()) {
                 timer.rescheduleCutoff();
             }
-            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
-                    userId, packageName)) {
-                mStateChangedListener
-                        .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
-            }
+            mStateChangedListener.onControllerStateChanged(
+                    maybeUpdateConstraintForPkgLocked(
+                            sElapsedRealtimeClock.millis(), userId, packageName));
         }
         if (restrictedChanges.size() > 0) {
             mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
@@ -2486,27 +2650,19 @@
                             Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
                         }
 
-                        long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
-                                pkg.packageName);
-                        if (timeRemainingMs <= 50) {
-                            // Less than 50 milliseconds left. Start process of shutting down jobs.
+                        final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+                                sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+                        if (changedJobs.size() > 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
-                            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
-                                    pkg.userId, pkg.packageName)) {
-                                mStateChangedListener.onControllerStateChanged(
-                                        mTrackedJobs.get(pkg.userId, pkg.packageName));
-                            }
+                            mStateChangedListener.onControllerStateChanged(changedJobs);
                         } else {
                             // This could potentially happen if an old session phases out while a
                             // job is currently running.
                             // Reschedule message
-                            Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
-                            timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
-                                    pkg.packageName);
                             if (DEBUG) {
-                                Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
+                                Slog.d(TAG, pkg + " had early REACHED_QUOTA message");
                             }
-                            sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+                            mPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
                         }
                         break;
                     }
@@ -2516,26 +2672,19 @@
                             Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
                         }
 
-                        long timeRemainingMs = getRemainingEJExecutionTimeLocked(
-                                pkg.userId, pkg.packageName);
-                        if (timeRemainingMs <= 0) {
+                        final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+                                sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+                        if (changedJobs.size() > 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
-                            if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
-                                    pkg.userId, pkg.packageName)) {
-                                mStateChangedListener.onControllerStateChanged(
-                                        mTrackedJobs.get(pkg.userId, pkg.packageName));
-                            }
+                            mStateChangedListener.onControllerStateChanged(changedJobs);
                         } else {
                             // This could potentially happen if an old session phases out while a
                             // job is currently running.
                             // Reschedule message
-                            Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
-                            timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
-                                    pkg.userId, pkg.packageName);
                             if (DEBUG) {
-                                Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ");
+                                Slog.d(TAG, pkg + " had early REACHED_EJ_QUOTA message");
                             }
-                            sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+                            mEJPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
                         }
                         break;
                     }
@@ -2553,11 +2702,9 @@
                         if (DEBUG) {
                             Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
                         }
-                        if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
-                                userId, packageName)) {
-                            mStateChangedListener.onControllerStateChanged(
-                                    mTrackedJobs.get(userId, packageName));
-                        }
+                        mStateChangedListener.onControllerStateChanged(
+                                maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+                                        userId, packageName));
                         break;
                     }
                     case MSG_UID_PROCESS_STATE_CHANGED: {
@@ -2781,6 +2928,12 @@
         static final String KEY_IN_QUOTA_BUFFER_MS =
                 QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
         @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+                QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_low";
+        @VisibleForTesting
+        static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+                QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min";
+        @VisibleForTesting
         static final String KEY_WINDOW_SIZE_ACTIVE_MS =
                 QC_CONSTANT_PREFIX + "window_size_active_ms";
         @VisibleForTesting
@@ -2890,6 +3043,8 @@
                 10 * 60 * 1000L; // 10 minutes
         private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
                 30 * 1000L; // 30 seconds
+        private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f;
+        private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f;
         private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
                 DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
         private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
@@ -2951,6 +3106,22 @@
         public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
 
         /**
+         * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+         * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum
+         * surplus of this amount of remaining allowed time before we start running low priority
+         * jobs.
+         */
+        public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
+
+        /**
+         * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by
+         * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum
+         * surplus of this amount of remaining allowed time before we start running min priority
+         * jobs.
+         */
+        public float ALLOWED_TIME_SURPLUS_PRIORITY_MIN = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
+        /**
          * The quota window size of the particular standby bucket. Apps in this standby bucket are
          * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
          * WINDOW_SIZE_MS.
@@ -3188,6 +3359,8 @@
                 @NonNull String key) {
             switch (key) {
                 case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+                case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW:
+                case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN:
                 case KEY_IN_QUOTA_BUFFER_MS:
                 case KEY_MAX_EXECUTION_TIME_MS:
                 case KEY_WINDOW_SIZE_ACTIVE_MS:
@@ -3407,6 +3580,7 @@
             final DeviceConfig.Properties properties = DeviceConfig.getProperties(
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS,
+                    KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
                     KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
                     KEY_WINDOW_SIZE_WORKING_MS,
                     KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
@@ -3414,6 +3588,12 @@
             ALLOWED_TIME_PER_PERIOD_MS =
                     properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
                             DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+            ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+                    properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW,
+                            DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW);
+            ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+                    properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
+                            DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN);
             IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
                     DEFAULT_IN_QUOTA_BUFFER_MS);
             MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
@@ -3455,6 +3635,23 @@
                 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
                 mShouldReevaluateConstraints = true;
             }
+            // Low priority surplus should be in the range [0, .9]. A value of 1 would essentially
+            // mean never run low priority jobs.
+            float newAllowedTimeSurplusPriorityLow =
+                    Math.max(0f, Math.min(.9f, ALLOWED_TIME_SURPLUS_PRIORITY_LOW));
+            if (Float.compare(
+                    mAllowedTimeSurplusPriorityLow, newAllowedTimeSurplusPriorityLow) != 0) {
+                mAllowedTimeSurplusPriorityLow = newAllowedTimeSurplusPriorityLow;
+                mShouldReevaluateConstraints = true;
+            }
+            // Min priority surplus should be in the range [0, mAllowedTimeSurplusPriorityLow].
+            float newAllowedTimeSurplusPriorityMin = Math.max(0f,
+                    Math.min(mAllowedTimeSurplusPriorityLow, ALLOWED_TIME_SURPLUS_PRIORITY_MIN));
+            if (Float.compare(
+                    mAllowedTimeSurplusPriorityMin, newAllowedTimeSurplusPriorityMin) != 0) {
+                mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin;
+                mShouldReevaluateConstraints = true;
+            }
             long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
                     Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
             if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
@@ -3627,6 +3824,10 @@
             pw.println("QuotaController:");
             pw.increaseIndent();
             pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
+            pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW)
+                    .println();
+            pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN)
+                    .println();
             pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
             pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
             pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
@@ -3750,6 +3951,16 @@
     }
 
     @VisibleForTesting
+    float getAllowedTimeSurplusPriorityLow() {
+        return mAllowedTimeSurplusPriorityLow;
+    }
+
+    @VisibleForTesting
+    float getAllowedTimeSurplusPriorityMin() {
+        return mAllowedTimeSurplusPriorityMin;
+    }
+
+    @VisibleForTesting
     @NonNull
     int[] getBucketMaxJobCounts() {
         return mMaxBucketJobCounts;
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 0ad70e4..ebf42b8 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -183,7 +183,7 @@
             COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
             COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
             COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR,
-            COMPRESS_TIME ? 32 * ONE_MINUTE : 3 * ONE_DAY
+            COMPRESS_TIME ? 32 * ONE_MINUTE : 8 * ONE_DAY
     };
 
     /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index a157517..6a685a7 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -64,6 +64,8 @@
         "libwilhelm",
     ],
 
+    header_libs: ["bionic_libc_platform_headers"],
+
     compile_multilib: "both",
 
     cflags: [
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 12083b6..815f945 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -15,6 +15,7 @@
 
 #include <android-base/macros.h>
 #include <binder/IPCThreadState.h>
+#include <bionic/pac.h>
 #include <hwbinder/IPCThreadState.h>
 #include <utils/Log.h>
 #include <cutils/memory.h>
@@ -182,6 +183,10 @@
       ALOGV("app_process main with argv: %s", argv_String.string());
     }
 
+    // Because of applications that are using PAC instructions incorrectly, PAC
+    // is disabled in application processes for now.
+    ScopedDisablePAC x;
+
     AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
     // Process command line arguments
     // ignore argv[0]
diff --git a/core/api/current.txt b/core/api/current.txt
index 78d4b4d..421107e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -134,6 +134,9 @@
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
+    field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
+    field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE";
+    field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
     field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -215,6 +218,8 @@
     field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
     field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
     field public static final String PHONE = "android.permission-group.PHONE";
+    field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL";
+    field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL";
     field public static final String SENSORS = "android.permission-group.SENSORS";
     field public static final String SMS = "android.permission-group.SMS";
     field public static final String STORAGE = "android.permission-group.STORAGE";
@@ -953,6 +958,7 @@
     field public static final int letterSpacing = 16843958; // 0x10104b6
     field public static final int level = 16844032; // 0x1010500
     field public static final int lineBreakStyle = 16844365; // 0x101064d
+    field public static final int lineBreakWordStyle = 16844366; // 0x101064e
     field public static final int lineHeight = 16844159; // 0x101057f
     field public static final int lineSpacingExtra = 16843287; // 0x1010217
     field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -7295,10 +7301,10 @@
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
+    method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
     method @NonNull public String getEnrollmentSpecificId();
     method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
@@ -7342,6 +7348,7 @@
     method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
     method public int getPersonalAppsSuspendedReasons(@NonNull android.content.ComponentName);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
     method public int getRequiredPasswordComplexity();
     method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
@@ -7486,6 +7493,7 @@
     method public boolean setPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName, @Nullable java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setPersonalAppsSuspended(@NonNull android.content.ComponentName, boolean);
+    method public void setPreferentialNetworkServiceConfig(@NonNull android.app.admin.PreferentialNetworkServiceConfig);
     method public void setPreferentialNetworkServiceEnabled(boolean);
     method public void setProfileEnabled(@NonNull android.content.ComponentName);
     method public void setProfileName(@NonNull android.content.ComponentName, String);
@@ -7709,29 +7717,29 @@
     ctor public DevicePolicyResources();
   }
 
-  public static final class DevicePolicyResources.Drawable {
-    field public static final int INVALID_ID = -1; // 0xffffffff
-    field public static final int WORK_PROFILE_ICON = 1; // 0x1
-    field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0
-    field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2
-    field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3
+  public static final class DevicePolicyResources.Drawables {
+    field public static final String UNDEFINED = "UNDEFINED";
+    field public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
+    field public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
+    field public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
+    field public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
   }
 
-  public static final class DevicePolicyResources.Drawable.Source {
-    field public static final int HOME_WIDGET = 2; // 0x2
-    field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3
-    field public static final int NOTIFICATION = 0; // 0x0
-    field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1
-    field public static final int QUICK_SETTINGS = 4; // 0x4
-    field public static final int STATUS_BAR = 5; // 0x5
-    field public static final int UNDEFINED = -1; // 0xffffffff
+  public static final class DevicePolicyResources.Drawables.Source {
+    field public static final String HOME_WIDGET = "HOME_WIDGET";
+    field public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
+    field public static final String NOTIFICATION = "NOTIFICATION";
+    field public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
+    field public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
+    field public static final String STATUS_BAR = "STATUS_BAR";
+    field public static final String UNDEFINED = "UNDEFINED";
   }
 
-  public static final class DevicePolicyResources.Drawable.Style {
-    field public static final int DEFAULT = -1; // 0xffffffff
-    field public static final int OUTLINE = 2; // 0x2
-    field public static final int SOLID_COLORED = 0; // 0x0
-    field public static final int SOLID_NOT_COLORED = 1; // 0x1
+  public static final class DevicePolicyResources.Drawables.Style {
+    field public static final String DEFAULT = "DEFAULT";
+    field public static final String OUTLINE = "OUTLINE";
+    field public static final String SOLID_COLORED = "SOLID_COLORED";
+    field public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
   }
 
   public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable {
@@ -7771,6 +7779,32 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR;
   }
 
+  public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int[] getExcludedUids();
+    method @NonNull public int[] getIncludedUids();
+    method public int getNetworkId();
+    method public boolean isEnabled();
+    method public boolean isFallbackToDefaultConnectionAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PreferentialNetworkServiceConfig> CREATOR;
+    field public static final int PREFERENTIAL_NETWORK_ID_1 = 1; // 0x1
+    field public static final int PREFERENTIAL_NETWORK_ID_2 = 2; // 0x2
+    field public static final int PREFERENTIAL_NETWORK_ID_3 = 3; // 0x3
+    field public static final int PREFERENTIAL_NETWORK_ID_4 = 4; // 0x4
+    field public static final int PREFERENTIAL_NETWORK_ID_5 = 5; // 0x5
+  }
+
+  public static final class PreferentialNetworkServiceConfig.Builder {
+    ctor public PreferentialNetworkServiceConfig.Builder();
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig build();
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setEnabled(boolean);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setExcludedUids(@NonNull int[]);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(boolean);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setIncludedUids(@NonNull int[]);
+    method @NonNull public android.app.admin.PreferentialNetworkServiceConfig.Builder setNetworkId(int);
+  }
+
   public class SecurityLog {
     ctor public SecurityLog();
     field public static final int LEVEL_ERROR = 3; // 0x3
@@ -8866,6 +8900,7 @@
     method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout();
     method public int getLeMaximumAdvertisingDataLength();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getMaxConnectedAudioDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
     method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
@@ -9752,18 +9787,54 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
   }
 
-  public final class BluetoothLeAudioCodecConfig {
+  public final class BluetoothLeAudioCodecConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getBitsPerSample();
+    method public int getChannelMode();
     method @NonNull public String getCodecName();
+    method public int getCodecPriority();
     method public int getCodecType();
+    method public int getFrameDuration();
     method public static int getMaxCodecType();
+    method public int getOctetsPerFrame();
+    method public int getSampleRate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+    field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+    field public static final int BITS_PER_SAMPLE_32 = 3; // 0x3
+    field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+    field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+    field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+    field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+    field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothLeAudioCodecConfig> CREATOR;
+    field public static final int FRAME_DURATION_10000 = 2; // 0x2
+    field public static final int FRAME_DURATION_7500 = 1; // 0x1
+    field public static final int FRAME_DURATION_NONE = 0; // 0x0
+    field public static final int SAMPLE_RATE_16000 = 2; // 0x2
+    field public static final int SAMPLE_RATE_24000 = 3; // 0x3
+    field public static final int SAMPLE_RATE_32000 = 4; // 0x4
+    field public static final int SAMPLE_RATE_44100 = 5; // 0x5
+    field public static final int SAMPLE_RATE_48000 = 6; // 0x6
+    field public static final int SAMPLE_RATE_8000 = 1; // 0x1
+    field public static final int SAMPLE_RATE_NONE = 0; // 0x0
     field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
     field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
   }
 
   public static final class BluetoothLeAudioCodecConfig.Builder {
     ctor public BluetoothLeAudioCodecConfig.Builder();
+    ctor public BluetoothLeAudioCodecConfig.Builder(@NonNull android.bluetooth.BluetoothLeAudioCodecConfig);
     method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setBitsPerSample(int);
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setChannelMode(int);
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecPriority(int);
     method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setFrameDuration(int);
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setOctetsPerFrame(int);
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setSampleRate(int);
   }
 
   public final class BluetoothManager {
@@ -17618,12 +17689,16 @@
   public final class LineBreakConfig {
     ctor public LineBreakConfig();
     method public int getLineBreakStyle();
-    method public void set(@Nullable android.graphics.text.LineBreakConfig);
+    method public int getLineBreakWordStyle();
+    method public void set(@NonNull android.graphics.text.LineBreakConfig);
     method public void setLineBreakStyle(int);
+    method public void setLineBreakWordStyle(int);
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
+    field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
+    field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
   }
 
   public class LineBreaker {
@@ -20956,6 +21031,7 @@
     method public boolean isSink();
     method public boolean isSource();
     field public static final int TYPE_AUX_LINE = 19; // 0x13
+    field public static final int TYPE_BLE_BROADCAST = 30; // 0x1e
     field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
     field public static final int TYPE_BLE_SPEAKER = 27; // 0x1b
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
@@ -26171,6 +26247,15 @@
 
 package android.media.tv {
 
+  public final class AitInfo implements android.os.Parcelable {
+    ctor public AitInfo(int, int);
+    method public int describeContents();
+    method public int getType();
+    method public int getVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.AitInfo> CREATOR;
+  }
+
   public final class TvContentRating {
     method public boolean contains(@NonNull android.media.tv.TvContentRating);
     method public static android.media.tv.TvContentRating createRating(String, String, String, java.lang.String...);
@@ -26741,12 +26826,14 @@
   public abstract static class TvInputService.Session implements android.view.KeyEvent.Callback {
     ctor public TvInputService.Session(android.content.Context);
     method public void layoutSurface(int, int, int, int);
+    method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
     method public void notifyChannelRetuned(android.net.Uri);
     method public void notifyContentAllowed();
     method public void notifyContentBlocked(@NonNull android.media.tv.TvContentRating);
     method public void notifyTimeShiftStatusChanged(int);
     method public void notifyTrackSelected(int, String);
     method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>);
+    method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyVideoAvailable();
     method public void notifyVideoUnavailable(int);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -26760,6 +26847,7 @@
     method public abstract void onRelease();
     method public boolean onSelectTrack(int, @Nullable String);
     method public abstract void onSetCaptionEnabled(boolean);
+    method public void onSetInteractiveAppNotificationEnabled(boolean);
     method public abstract void onSetStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
     method public void onSurfaceChanged(int, int, int);
@@ -26861,6 +26949,7 @@
     method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
     method public void setCallback(@Nullable android.media.tv.TvView.TvInputCallback);
     method public void setCaptionEnabled(boolean);
+    method public void setInteractiveAppNotificationEnabled(boolean);
     method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
     method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
@@ -26887,6 +26976,7 @@
 
   public abstract static class TvView.TvInputCallback {
     ctor public TvView.TvInputCallback();
+    method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
     method public void onChannelRetuned(String, android.net.Uri);
     method public void onConnectionFailed(String);
     method public void onContentAllowed(String);
@@ -26919,26 +27009,76 @@
 
   public final class TvInteractiveAppManager {
     method @NonNull public java.util.List<android.media.tv.interactive.TvInteractiveAppInfo> getTvInteractiveAppServiceList();
+    method public void registerCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback, @NonNull java.util.concurrent.Executor);
+    method public void unregisterCallback(@NonNull android.media.tv.interactive.TvInteractiveAppManager.TvInteractiveAppCallback);
+    field public static final int ERROR_BLOCKED = 5; // 0x5
+    field public static final int ERROR_ENCRYPTED = 6; // 0x6
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ERROR_RESOURCE_UNAVAILABLE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+    field public static final int ERROR_UNKNOWN_CHANNEL = 7; // 0x7
+    field public static final int ERROR_WEAK_SIGNAL = 3; // 0x3
+    field public static final int INTERACTIVE_APP_STATE_ERROR = 3; // 0x3
+    field public static final int INTERACTIVE_APP_STATE_RUNNING = 2; // 0x2
+    field public static final int INTERACTIVE_APP_STATE_STOPPED = 1; // 0x1
+    field public static final int SERVICE_STATE_ERROR = 4; // 0x4
+    field public static final int SERVICE_STATE_PREPARING = 2; // 0x2
+    field public static final int SERVICE_STATE_READY = 3; // 0x3
+    field public static final int SERVICE_STATE_UNREALIZED = 1; // 0x1
+  }
+
+  public abstract static class TvInteractiveAppManager.TvInteractiveAppCallback {
+    ctor public TvInteractiveAppManager.TvInteractiveAppCallback();
+    method public void onTvInteractiveAppServiceStateChanged(@NonNull String, int, int, int);
   }
 
   public abstract class TvInteractiveAppService extends android.app.Service {
     ctor public TvInteractiveAppService();
+    method public final void notifyStateChanged(int, int, int);
     method public final android.os.IBinder onBind(android.content.Intent);
+    method @Nullable public abstract android.media.tv.interactive.TvInteractiveAppService.Session onCreateSession(@NonNull String, int);
+    method public abstract void onPrepare(int);
     field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvInteractiveAppService";
     field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
   }
 
+  public abstract static class TvInteractiveAppService.Session implements android.view.KeyEvent.Callback {
+    ctor public TvInteractiveAppService.Session(@NonNull android.content.Context);
+    method public void layoutSurface(int, int, int, int);
+    method public final void notifyBiInteractiveAppCreated(@NonNull android.net.Uri, @Nullable String);
+    method public void notifySessionStateChanged(int, int);
+    method public void onCreateBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method public void onDestroyBiInteractiveApp(@NonNull String);
+    method public boolean onKeyDown(int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyLongPress(int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
+    method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
+    method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+    method public void onStartInteractiveApp();
+    method public void onStopInteractiveApp();
+    method public void onSurfaceChanged(int, int, int);
+    method public void onTuned(@NonNull android.net.Uri);
+  }
+
   public class TvInteractiveAppView extends android.view.ViewGroup {
     ctor public TvInteractiveAppView(@NonNull android.content.Context);
     ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     ctor public TvInteractiveAppView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
     method public void clearCallback();
+    method public void createBiInteractiveApp(@NonNull android.net.Uri, @Nullable android.os.Bundle);
+    method public void destroyBiInteractiveApp(@NonNull String);
+    method public void prepareInteractiveApp(@NonNull String, int);
     method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback);
+    method public int setTvView(@Nullable android.media.tv.TvView);
     method public void startInteractiveApp();
+    method public void stopInteractiveApp();
   }
 
   public abstract static class TvInteractiveAppView.TvInteractiveAppCallback {
     ctor public TvInteractiveAppView.TvInteractiveAppCallback();
+    method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
+    method public void onStateChanged(@NonNull String, int, int);
   }
 
 }
@@ -33039,9 +33179,13 @@
     method @NonNull public int[] areEffectsSupported(@NonNull int...);
     method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+    method @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile();
     method public int getId();
     method @NonNull public int[] getPrimitiveDurations(@NonNull int...);
+    method public float getQFactor();
+    method public float getResonantFrequency();
     method public abstract boolean hasAmplitudeControl();
+    method public boolean hasFrequencyControl();
     method public abstract boolean hasVibrator();
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long, android.media.AudioAttributes);
@@ -33367,6 +33511,17 @@
 
 }
 
+package android.os.vibrator {
+
+  public final class VibratorFrequencyProfile {
+    method public float getMaxAmplitudeMeasurementInterval();
+    method @FloatRange(from=0, to=1) @NonNull public float[] getMaxAmplitudeMeasurements();
+    method public float getMaxFrequency();
+    method public float getMinFrequency();
+  }
+
+}
+
 package android.preference {
 
   @Deprecated public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -40517,6 +40672,7 @@
   public final class Call {
     method public void addConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void answer(int);
+    method public void answerCall(@NonNull android.telecom.CallEndpoint, int);
     method public void conference(android.telecom.Call);
     method public void deflect(android.net.Uri);
     method public void disconnect();
@@ -40537,7 +40693,9 @@
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
     method public void postDialContinue(boolean);
-    method public void pullExternalCall();
+    method public void pullCall();
+    method @Deprecated public void pullExternalCall();
+    method public void pushCall(@NonNull android.telecom.CallEndpoint);
     method public void putExtras(android.os.Bundle);
     method public void registerCallback(android.telecom.Call.Callback);
     method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
@@ -40580,7 +40738,10 @@
 
   public abstract static class Call.Callback {
     ctor public Call.Callback();
+    method public void onAnswerFailed(@NonNull android.telecom.CallEndpoint, int);
     method public void onCallDestroyed(android.telecom.Call);
+    method public void onCallPullFailed(int);
+    method public void onCallPushFailed(@NonNull android.telecom.CallEndpoint, int);
     method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
     method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
     method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
@@ -40596,11 +40757,22 @@
     method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
     method public void onStateChanged(android.telecom.Call, int);
     method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall);
+    field public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3; // 0x3
+    field public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2
+    field public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1
+    field public static final int ANSWER_FAILED_UNKNOWN_REASON = 0; // 0x0
     field public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1; // 0x1
     field public static final int HANDOVER_FAILURE_NOT_SUPPORTED = 2; // 0x2
     field public static final int HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL = 4; // 0x4
     field public static final int HANDOVER_FAILURE_UNKNOWN = 5; // 0x5
     field public static final int HANDOVER_FAILURE_USER_REJECTED = 3; // 0x3
+    field public static final int PULL_FAILED_ENDPOINT_REJECTED = 2; // 0x2
+    field public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1; // 0x1
+    field public static final int PULL_FAILED_UNKNOWN_REASON = 0; // 0x0
+    field public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3; // 0x3
+    field public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2; // 0x2
+    field public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1; // 0x1
+    field public static final int PUSH_FAILED_UNKNOWN_REASON = 0; // 0x0
   }
 
   public static class Call.Details {
@@ -40608,6 +40780,8 @@
     method public boolean can(int);
     method public static String capabilitiesToString(int);
     method public android.telecom.PhoneAccountHandle getAccountHandle();
+    method @Nullable public android.telecom.CallEndpoint getActiveCallEndpoint();
+    method @NonNull public java.util.Set<android.telecom.CallEndpoint> getAvailableCallEndpoints();
     method public int getCallCapabilities();
     method public int getCallDirection();
     method public int getCallProperties();
@@ -40701,6 +40875,34 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
+  public final class CallEndpoint implements android.os.Parcelable {
+    ctor public CallEndpoint(@NonNull android.os.ParcelUuid, @NonNull CharSequence, int, @NonNull android.content.ComponentName);
+    method public int describeContents();
+    method @NonNull public CharSequence getDescription();
+    method @NonNull public android.os.ParcelUuid getIdentifier();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR;
+    field public static final int ENDPOINT_TYPE_TETHERED = 2; // 0x2
+    field public static final int ENDPOINT_TYPE_UNTETHERED = 1; // 0x1
+  }
+
+  public interface CallEndpointCallback {
+    method public void onCallEndpointSessionActivationTimeout();
+    method public void onCallEndpointSessionDeactivated();
+  }
+
+  public class CallEndpointSession {
+    method public void setCallEndpointSessionActivated();
+    method public void setCallEndpointSessionActivationFailed(int);
+    method public void setCallEndpointSessionDeactivated();
+    field public static final int ACTIVATION_FAILURE_REJECTED = 1; // 0x1
+    field public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0; // 0x0
+    field public static final int ANSWER_REQUEST = 1; // 0x1
+    field public static final int PLACE_REQUEST = 3; // 0x3
+    field public static final int PUSH_REQUEST = 2; // 0x2
+  }
+
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -40970,7 +41172,6 @@
     field public static final int PROPERTY_IS_RTT = 256; // 0x100
     field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400
     field public static final int PROPERTY_SELF_MANAGED = 128; // 0x80
-    field public static final int PROPERTY_TETHERED_CALL = 16384; // 0x4000
     field public static final int PROPERTY_WIFI = 8; // 0x8
     field public static final int STATE_ACTIVE = 4; // 0x4
     field public static final int STATE_DIALING = 3; // 0x3
@@ -41104,6 +41305,8 @@
     field public static final int OTHER = 9; // 0x9
     field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
     field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
+    field public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+    field public static final String REASON_ENDPOINT_SESSION_DEACTIVATED = "REASON_ENDPOINT_SESSION_DEACTIVATED";
     field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
     field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
     field public static final int REJECTED = 6; // 0x6
@@ -41132,6 +41335,7 @@
     method public void onBringToForeground(boolean);
     method public void onCallAdded(android.telecom.Call);
     method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method @NonNull public android.telecom.CallEndpointCallback onCallEndpointActivationRequested(@NonNull android.telecom.CallEndpoint, @NonNull android.telecom.CallEndpointSession) throws java.lang.UnsupportedOperationException;
     method public void onCallRemoved(android.telecom.Call);
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle);
@@ -41394,11 +41598,12 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
+    method @NonNull public java.util.Set<android.telecom.CallEndpoint> getCallEndpoints();
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
+    method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_OWN_CALLS}) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
     method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int);
     method @Nullable public String getSystemDialerPackage();
@@ -41414,10 +41619,12 @@
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public boolean isTtySupported();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
     method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
+    method public void registerCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle);
+    method public void unregisterCallEndpoints(@NonNull java.util.Set<android.telecom.CallEndpoint>);
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER";
     field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
@@ -41461,6 +41668,7 @@
     field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
     field public static final String EXTRA_PICTURE_URI = "android.telecom.extra.PICTURE_URI";
     field public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY";
+    field public static final String EXTRA_START_CALL_ON_ENDPOINT = "android.telecom.extra.START_CALL_ON_ENDPOINT";
     field public static final String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
     field public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
@@ -41472,6 +41680,7 @@
     field public static final String METADATA_IN_CALL_SERVICE_CAR_MODE_UI = "android.telecom.IN_CALL_SERVICE_CAR_MODE_UI";
     field public static final String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
+    field public static final String METADATA_STREAMING_TETHERED_CALLS = "android.telecom.STREAMING_TETHERED_CALLS";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
     field public static final int PRESENTATION_PAYPHONE = 4; // 0x4
     field public static final int PRESENTATION_RESTRICTED = 2; // 0x2
@@ -48308,6 +48517,7 @@
     method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
     method public default int getBufferTransformHint();
     method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+    method public default void setTouchableRegion(@Nullable android.graphics.Region);
   }
 
   @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
@@ -48684,6 +48894,7 @@
     method public static int[] getDeviceIds();
     method public int getId();
     method public android.view.KeyCharacterMap getKeyCharacterMap();
+    method public int getKeyCodeForKeyLocation(int);
     method public int getKeyboardType();
     method @NonNull public android.hardware.lights.LightsManager getLightsManager();
     method public android.view.InputDevice.MotionRange getMotionRange(int);
@@ -55051,6 +55262,7 @@
   public abstract class WebSettings {
     ctor public WebSettings();
     method @Deprecated public abstract boolean enableSmoothTransition();
+    method public boolean getAllowAlgorithmicDarkening();
     method public abstract boolean getAllowContentAccess();
     method public abstract boolean getAllowFileAccess();
     method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -55072,7 +55284,7 @@
     method public abstract boolean getDomStorageEnabled();
     method public abstract String getFantasyFontFamily();
     method public abstract String getFixedFontFamily();
-    method public int getForceDark();
+    method @Deprecated public int getForceDark();
     method public abstract boolean getJavaScriptCanOpenWindowsAutomatically();
     method public abstract boolean getJavaScriptEnabled();
     method public abstract android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
@@ -55095,6 +55307,7 @@
     method public abstract int getTextZoom();
     method public abstract boolean getUseWideViewPort();
     method public abstract String getUserAgentString();
+    method public void setAllowAlgorithmicDarkening(boolean);
     method public abstract void setAllowContentAccess(boolean);
     method public abstract void setAllowFileAccess(boolean);
     method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
@@ -55116,7 +55329,7 @@
     method @Deprecated public abstract void setEnableSmoothTransition(boolean);
     method public abstract void setFantasyFontFamily(String);
     method public abstract void setFixedFontFamily(String);
-    method public void setForceDark(int);
+    method @Deprecated public void setForceDark(int);
     method @Deprecated public abstract void setGeolocationDatabasePath(String);
     method public abstract void setGeolocationEnabled(boolean);
     method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean);
@@ -55147,9 +55360,9 @@
     method public abstract void setUserAgentString(@Nullable String);
     method public abstract boolean supportMultipleWindows();
     method public abstract boolean supportZoom();
-    field public static final int FORCE_DARK_AUTO = 1; // 0x1
-    field public static final int FORCE_DARK_OFF = 0; // 0x0
-    field public static final int FORCE_DARK_ON = 2; // 0x2
+    field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+    field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
     field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1
     field public static final int LOAD_CACHE_ONLY = 3; // 0x3
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
@@ -57314,8 +57527,8 @@
     method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int);
     method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int);
     method public void setViewVisibility(@IdRes int, int);
-    method public void showNext(@IdRes int);
-    method public void showPrevious(@IdRes int);
+    method @Deprecated public void showNext(@IdRes int);
+    method @Deprecated public void showPrevious(@IdRes int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR;
     field public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2a4e7a6..ee31e13 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -63,6 +63,8 @@
 
   public class DevicePolicyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void acknowledgeNewUserDisclaimer();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearLogoutUser();
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getLogoutUser();
     field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
   }
 
@@ -78,16 +80,27 @@
     method @NonNull @WorkerThread public android.app.usage.NetworkStats querySummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
     method @NonNull @WorkerThread public android.app.usage.NetworkStats.Bucket querySummaryForDevice(@NonNull android.net.NetworkTemplate, long, long);
     method @NonNull @WorkerThread public android.app.usage.NetworkStats queryTaggedSummary(@NonNull android.net.NetworkTemplate, long, long) throws java.lang.SecurityException;
+    method public void registerUsageCallback(@NonNull android.net.NetworkTemplate, long, @NonNull java.util.concurrent.Executor, @NonNull android.app.usage.NetworkStatsManager.UsageCallback);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setDefaultGlobalAlert(long);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setPollOnOpen(boolean);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setStatsProviderWarningAndLimitAsync(@NonNull String, long, long);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void setUidForeground(int, boolean);
   }
 
+  public abstract static class NetworkStatsManager.UsageCallback {
+    method public void onThresholdReached(@NonNull android.net.NetworkTemplate);
+  }
+
 }
 
 package android.bluetooth {
 
+  public class BluetoothFrameworkInitializer {
+    method public static void registerServiceWrappers();
+    method public static void setBinderCallsStatsInitializer(@NonNull java.util.function.Consumer<android.content.Context>);
+    method public static void setBluetoothServiceManager(@NonNull android.os.BluetoothServiceManager);
+  }
+
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public android.net.TetheringManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheredInterfaceCallback);
   }
@@ -266,6 +279,10 @@
     method public int getResourceId();
   }
 
+  public class LocalSocket implements java.io.Closeable {
+    ctor public LocalSocket(@NonNull java.io.FileDescriptor);
+  }
+
   public class NetworkIdentity {
     method public int getOemManaged();
     method public int getRatType();
@@ -319,6 +336,20 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR;
   }
 
+  public class NetworkStatsCollection {
+    method @NonNull public java.util.Map<android.net.NetworkStatsCollection.Key,android.net.NetworkStatsHistory> getEntries();
+  }
+
+  public static final class NetworkStatsCollection.Builder {
+    ctor public NetworkStatsCollection.Builder(long);
+    method @NonNull public android.net.NetworkStatsCollection.Builder addEntry(@NonNull android.net.NetworkStatsCollection.Key, @NonNull android.net.NetworkStatsHistory);
+    method @NonNull public android.net.NetworkStatsCollection build();
+  }
+
+  public static class NetworkStatsCollection.Key {
+    ctor public NetworkStatsCollection.Key(@NonNull java.util.Set<android.net.NetworkIdentity>, int, int, int);
+  }
+
   public final class NetworkStatsHistory implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries();
@@ -399,6 +430,7 @@
   }
 
   public class TrafficStats {
+    method public static void attachSocketTagger();
     method public static void init(@NonNull android.content.Context);
   }
 
@@ -432,6 +464,21 @@
     method public final void markVintfStability();
   }
 
+  public class BluetoothServiceManager {
+    method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer();
+  }
+
+  public static class BluetoothServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor public BluetoothServiceManager.ServiceNotFoundException(@NonNull String);
+  }
+
+  public static final class BluetoothServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder get();
+    method @NonNull public android.os.IBinder getOrThrow() throws android.os.BluetoothServiceManager.ServiceNotFoundException;
+    method public void register(@NonNull android.os.IBinder);
+    method @Nullable public android.os.IBinder tryGet();
+  }
+
   public class Build {
     method public static boolean isDebuggable();
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 34a8a8b..ea2a641 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -287,6 +287,7 @@
     field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
     field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+    field public static final String SEND_LOST_MODE_LOCATION_UPDATES = "android.permission.SEND_LOST_MODE_LOCATION_UPDATES";
     field public static final String SEND_SAFETY_CENTER_UPDATE = "android.permission.SEND_SAFETY_CENTER_UPDATE";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -320,6 +321,7 @@
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
     field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
+    field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -1021,13 +1023,13 @@
 package android.app.admin {
 
   public final class DevicePolicyDrawableResource implements android.os.Parcelable {
-    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int);
-    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int);
+    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @NonNull String, @DrawableRes int);
+    ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @DrawableRes int);
     method public int describeContents();
     method @DrawableRes public int getCallingPackageResourceId();
-    method public int getDrawableId();
-    method public int getDrawableSource();
-    method public int getDrawableStyle();
+    method @NonNull public String getDrawableId();
+    method @NonNull public String getDrawableSource();
+    method @NonNull public String getDrawableStyle();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR;
   }
@@ -1066,8 +1068,9 @@
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
-    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetDrawables(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void resetStrings(@NonNull String[]);
+    method @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setDrawables(@NonNull java.util.Set<android.app.admin.DevicePolicyDrawableResource>);
@@ -1079,6 +1082,7 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
     field @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE) public static final String ACTION_ESTABLISH_NETWORK_CONNECTION = "android.app.action.ESTABLISH_NETWORK_CONNECTION";
+    field public static final String ACTION_LOST_MODE_LOCATION_UPDATE = "android.app.action.LOST_MODE_LOCATION_UPDATE";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
@@ -1105,6 +1109,7 @@
     field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
     field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
+    field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -2373,9 +2378,11 @@
     field public static final int ACCESS_REJECTED = 2; // 0x2
     field public static final int ACCESS_UNKNOWN = 0; // 0x0
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_SILENCE_MODE_CHANGED = "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+    field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_SWITCH_BUFFER_SIZE = "android.bluetooth.device.action.SWITCH_BUFFER_SIZE";
     field public static final String DEVICE_TYPE_DEFAULT = "Default";
     field public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
     field public static final String DEVICE_TYPE_WATCH = "Watch";
+    field public static final String EXTRA_LOW_LATENCY_BUFFER_SIZE = "android.bluetooth.device.extra.LOW_LATENCY_BUFFER_SIZE";
     field public static final int METADATA_COMPANION_APP = 4; // 0x4
     field public static final int METADATA_DEVICE_TYPE = 17; // 0x11
     field public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; // 0x10
@@ -2416,6 +2423,15 @@
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean stopScoUsingVirtualVoiceCall();
   }
 
+  public final class BluetoothHeadsetClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headsetprofile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
@@ -2447,8 +2463,14 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
   }
 
-  public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile {
+  public final class BluetoothMapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.SEND_SMS}) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+    field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
   }
 
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
@@ -5221,6 +5243,7 @@
   public final class UsbPort {
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableLimitPowerTransfer(boolean);
     method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbData(boolean);
+    method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbDataWhileDocked();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int);
     field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1
@@ -5233,6 +5256,12 @@
     field public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; // 0x4
     field public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; // 0x3
     field public static final int ENABLE_USB_DATA_SUCCESS = 0; // 0x0
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4; // 0x4
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1; // 0x1
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5; // 0x5
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3; // 0x3
+    field public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0; // 0x0
   }
 
   public final class UsbPortStatus implements android.os.Parcelable {
@@ -5240,7 +5269,9 @@
     method public int getCurrentDataRole();
     method public int getCurrentMode();
     method public int getCurrentPowerRole();
+    method public int getPowerBrickStatus();
     method public int getSupportedRoleCombinations();
+    method @Nullable public int[] getUsbDataStatus();
     method public boolean isConnected();
     method public boolean isPowerTransferLimited();
     method public boolean isRoleCombinationSupported(int, int);
@@ -5254,9 +5285,19 @@
     field public static final int MODE_DFP = 2; // 0x2
     field public static final int MODE_NONE = 0; // 0x0
     field public static final int MODE_UFP = 1; // 0x1
+    field public static final int POWER_BRICK_STATUS_CONNECTED = 1; // 0x1
+    field public static final int POWER_BRICK_STATUS_DISCONNECTED = 2; // 0x2
+    field public static final int POWER_BRICK_STATUS_UNKNOWN = 0; // 0x0
     field public static final int POWER_ROLE_NONE = 0; // 0x0
     field public static final int POWER_ROLE_SINK = 2; // 0x2
     field public static final int POWER_ROLE_SOURCE = 1; // 0x1
+    field public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3; // 0x3
+    field public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6; // 0x6
+    field public static final int USB_DATA_STATUS_DISABLED_DOCK = 4; // 0x4
+    field public static final int USB_DATA_STATUS_DISABLED_FORCE = 5; // 0x5
+    field public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2; // 0x2
+    field public static final int USB_DATA_STATUS_ENABLED = 1; // 0x1
+    field public static final int USB_DATA_STATUS_UNKNOWN = 0; // 0x0
   }
 
 }
@@ -6761,6 +6802,7 @@
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]);
     method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int);
     method public boolean isLowestPriority(int);
@@ -8005,6 +8047,16 @@
     method public boolean isLocked();
   }
 
+  public class FrontendStatusReadiness {
+    method public int getStatusReadiness();
+    method public int getStatusType();
+    field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3
+    field public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE = 1; // 0x1
+    field public static final int FRONTEND_STATUS_READINESS_UNDEFINED = 0; // 0x0
+    field public static final int FRONTEND_STATUS_READINESS_UNSTABLE = 2; // 0x2
+    field public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED = 4; // 0x4
+  }
+
   public class Isdbs3FrontendCapabilities extends android.media.tv.tuner.frontend.FrontendCapabilities {
     method public int getCodeRateCapability();
     method public int getModulationCapability();
@@ -9846,12 +9898,17 @@
 
   public final class PermissionControllerManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void getHibernationEligibility(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
     method public void getUnusedAppCount(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
     field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
     field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
+    field public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; // 0x0
+    field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; // 0x1
+    field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; // 0x2
+    field public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; // 0xffffffff
     field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
     field public static final int REASON_MALWARE = 1; // 0x1
   }
@@ -9869,6 +9926,7 @@
     method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
     method @BinderThread public void onGetGroupOfPlatformPermission(@NonNull String, @NonNull java.util.function.Consumer<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetHibernationEligibility(@NonNull String, @NonNull java.util.function.IntConsumer);
     method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
     method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
     method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
@@ -9893,6 +9951,8 @@
     method public int checkPermissionForDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public int checkPermissionForDataDeliveryFromDataSource(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
     method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
+    method public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
+    method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
     method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
     method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
@@ -14722,6 +14782,7 @@
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; // 0x6
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; // 0x4
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 5; // 0x5
+    field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN = 12; // 0xc
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 9; // 0x9
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 2; // 0x2
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 3; // 0x3
@@ -15690,6 +15751,7 @@
     method @Deprecated public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean);
     method @Deprecated public abstract void setUserAgent(int);
     method public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean);
+    field public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L; // 0xcccb1e0L
   }
 
   public class WebStorage {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e97ef6c..ff26ae8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
     field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL";
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+    field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
     field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
@@ -1180,9 +1181,23 @@
 
 package android.hardware.input {
 
+  public final class InputDeviceIdentifier implements android.os.Parcelable {
+    ctor public InputDeviceIdentifier(@NonNull String, int, int);
+    method public int describeContents();
+    method @NonNull public String getDescriptor();
+    method public int getProductId();
+    method public int getVendorId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.InputDeviceIdentifier> CREATOR;
+  }
+
   public final class InputManager {
     method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context);
+    method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
+    method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int);
+    method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
@@ -2740,6 +2755,7 @@
   public final class InputDevice implements android.os.Parcelable {
     method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable();
     method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable();
+    method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
   }
 
   public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index c9cbef2..897bab4 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -121,6 +121,11 @@
     ],
 }
 
+filegroup {
+    name: "ILogcatManagerService_aidl",
+    srcs: ["android/os/logcat/ILogcatManagerService.aidl"],
+}
+
 genrule {
     name: "statslog-framework-java-gen",
     tools: ["stats-log-api-gen"],
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3ddbe9e..ea62714 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -61,6 +61,7 @@
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.TransactionExecutor;
 import android.app.servertransaction.TransactionExecutorHelper;
+import android.bluetooth.BluetoothFrameworkInitializer;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
@@ -109,6 +110,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.BluetoothServiceManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -7919,6 +7921,7 @@
         StatsFrameworkInitializer.setStatsServiceManager(new StatsServiceManager());
         MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager());
         MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager());
+        BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
     }
 
     private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 68c69e5..0081234 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2344,11 +2344,11 @@
             Manifest.permission.USE_BIOMETRIC,
             Manifest.permission.ACTIVITY_RECOGNITION,
             Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
-            null,
+            Manifest.permission.READ_MEDIA_AUDIO,
             null, // no permission for OP_WRITE_MEDIA_AUDIO
-            null,
+            Manifest.permission.READ_MEDIA_VIDEO,
             null, // no permission for OP_WRITE_MEDIA_VIDEO
-            null,
+            Manifest.permission.READ_MEDIA_IMAGE,
             null, // no permission for OP_WRITE_MEDIA_IMAGES
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 7f849ef..9eb3e8f 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -29,9 +29,13 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.autofill.AutofillManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 
 /**
@@ -53,6 +57,10 @@
  */
 public class Application extends ContextWrapper implements ComponentCallbacks2 {
     private static final String TAG = "Application";
+
+    /** Whether to enable the check to detect "duplicate application instances". */
+    private static final boolean DEBUG_DUP_APP_INSTANCES = true;
+
     @UnsupportedAppUsage
     private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
             new ArrayList<ActivityLifecycleCallbacks>();
@@ -66,6 +74,13 @@
     @UnsupportedAppUsage
     public LoadedApk mLoadedApk;
 
+    @GuardedBy("sInstances")
+    private static final ArrayMap<Class<?>, Application> sInstances =
+            DEBUG_DUP_APP_INSTANCES ? new ArrayMap<>(1) : null;
+
+    // Only set when DEBUG_DUP_APP_INSTANCES is true.
+    private StackTrace mConstructorStackTrace;
+
     public interface ActivityLifecycleCallbacks {
 
         /**
@@ -231,6 +246,41 @@
 
     public Application() {
         super(null);
+        if (DEBUG_DUP_APP_INSTANCES) {
+            checkDuplicateInstances();
+        }
+    }
+
+    private void checkDuplicateInstances() {
+        final Class<?> myClass = this.getClass();
+
+        // We only activate this check for custom application classes.
+        // Otherwise, it'd misfire if multiple apps share the same process, if all of them use
+        // the same Application class (on the same classloader).
+        if (myClass == Application.class) {
+            return;
+        }
+        synchronized (sInstances) {
+            final Application firstInstance = sInstances.get(myClass);
+            if (firstInstance == null) {
+                this.mConstructorStackTrace = new StackTrace("First ctor was called here");
+                sInstances.put(myClass, this);
+                return;
+            }
+            final StackTrace currentStackTrace = new StackTrace("Current ctor was called here",
+                    firstInstance.mConstructorStackTrace);
+            this.mConstructorStackTrace = currentStackTrace;
+            Slog.wtf(TAG, "Application ctor called twice for " + myClass
+                    + " first LoadedApk=" + firstInstance.getLoadedApkInfo(),
+                    currentStackTrace);
+        }
+    }
+
+    private String getLoadedApkInfo() {
+        if (mLoadedApk == null) {
+            return "null";
+        }
+        return mLoadedApk + "/pkg=" + mLoadedApk.mPackageName;
     }
 
     /**
diff --git a/core/java/android/app/StackTrace.java b/core/java/android/app/StackTrace.java
index ec058f8..6a77abd 100644
--- a/core/java/android/app/StackTrace.java
+++ b/core/java/android/app/StackTrace.java
@@ -24,4 +24,8 @@
     public StackTrace(String message) {
         super(message);
     }
+
+    public StackTrace(String message, Throwable innerStackTrace) {
+        super(message, innerStackTrace);
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f0208c6..7f8e46e 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,7 +51,7 @@
 import android.app.usage.UsageStatsManager;
 import android.apphibernation.AppHibernationManager;
 import android.appwidget.AppWidgetManager;
-import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothFrameworkInitializer;
 import android.companion.CompanionDeviceManager;
 import android.companion.ICompanionDeviceManager;
 import android.companion.virtual.IVirtualDeviceManager;
@@ -348,13 +348,6 @@
                 return new MediaRouter(ctx);
             }});
 
-        registerService(Context.BLUETOOTH_SERVICE, BluetoothManager.class,
-                new CachedServiceFetcher<BluetoothManager>() {
-            @Override
-            public BluetoothManager createService(ContextImpl ctx) {
-                return new BluetoothManager(ctx);
-            }});
-
         registerService(Context.HDMI_CONTROL_SERVICE, HdmiControlManager.class,
                 new StaticServiceFetcher<HdmiControlManager>() {
             @Override
@@ -1544,6 +1537,7 @@
             ConnectivityFrameworkInitializer.registerServiceWrappers();
             JobSchedulerFrameworkInitializer.registerServiceWrappers();
             BlobStoreManagerFrameworkInitializer.initialize();
+            BluetoothFrameworkInitializer.registerServiceWrappers();
             TelephonyFrameworkInitializer.registerServiceWrappers();
             AppSearchManagerFrameworkInitializer.initialize();
             WifiFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java
index d32ff84..61ff11b 100644
--- a/core/java/android/app/admin/DevicePolicyDrawableResource.java
+++ b/core/java/android/app/admin/DevicePolicyDrawableResource.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -34,9 +35,9 @@
  */
 @SystemApi
 public final class DevicePolicyDrawableResource implements Parcelable {
-    private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId;
-    private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle;
-    private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableId String mDrawableId;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableStyle String mDrawableStyle;
+    @NonNull private final @DevicePolicyResources.UpdatableDrawableSource String mDrawableSource;
     private final @DrawableRes int mCallingPackageResourceId;
     @NonNull private ParcelableResource mResource;
 
@@ -59,9 +60,9 @@
      */
     public DevicePolicyDrawableResource(
             @NonNull Context context,
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @DrawableRes int callingPackageResourceId) {
         this(drawableId, drawableStyle, drawableSource, callingPackageResourceId,
                 new ParcelableResource(context, callingPackageResourceId,
@@ -69,11 +70,17 @@
     }
 
     private DevicePolicyDrawableResource(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @DrawableRes int callingPackageResourceId,
             @NonNull ParcelableResource resource) {
+
+        Objects.requireNonNull(drawableId);
+        Objects.requireNonNull(drawableStyle);
+        Objects.requireNonNull(drawableSource);
+        Objects.requireNonNull(resource);
+
         this.mDrawableId = drawableId;
         this.mDrawableStyle = drawableStyle;
         this.mDrawableSource = drawableSource;
@@ -98,34 +105,37 @@
      */
     public DevicePolicyDrawableResource(
             @NonNull Context context,
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             @DrawableRes int callingPackageResourceId) {
-       this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED,
+       this(context, drawableId, drawableStyle, Drawables.Source.UNDEFINED,
                callingPackageResourceId);
     }
 
     /**
      * Returns the ID of the drawable to update.
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableId
-    public int getDrawableId() {
+    public String getDrawableId() {
         return mDrawableId;
     }
 
     /**
      * Returns the style of the drawable to update
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableStyle
-    public int getDrawableStyle() {
+    public String getDrawableStyle() {
         return mDrawableStyle;
     }
 
     /**
      * Returns the source of the drawable to update.
      */
+    @NonNull
     @DevicePolicyResources.UpdatableDrawableSource
-    public int getDrawableSource() {
+    public String getDrawableSource() {
         return mDrawableSource;
     }
 
@@ -153,9 +163,9 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o;
-        return mDrawableId == other.mDrawableId
-                && mDrawableStyle == other.mDrawableStyle
-                && mDrawableSource == other.mDrawableSource
+        return mDrawableId.equals(other.mDrawableId)
+                && mDrawableStyle.equals(other.mDrawableStyle)
+                && mDrawableSource.equals(other.mDrawableSource)
                 && mCallingPackageResourceId == other.mCallingPackageResourceId
                 && mResource.equals(other.mResource);
     }
@@ -173,9 +183,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mDrawableId);
-        dest.writeInt(mDrawableStyle);
-        dest.writeInt(mDrawableSource);
+        dest.writeString(mDrawableId);
+        dest.writeString(mDrawableStyle);
+        dest.writeString(mDrawableSource);
         dest.writeInt(mCallingPackageResourceId);
         dest.writeTypedObject(mResource, flags);
     }
@@ -184,9 +194,9 @@
             new Creator<DevicePolicyDrawableResource>() {
                 @Override
                 public DevicePolicyDrawableResource createFromParcel(Parcel in) {
-                    int drawableId = in.readInt();
-                    int drawableStyle = in.readInt();
-                    int drawableSource = in.readInt();
+                    String drawableId = in.readString();
+                    String drawableStyle = in.readString();
+                    String drawableSource = in.readString();
                     int callingPackageResourceId = in.readInt();
                     ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR);
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fa0af2d..cbce8ac 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,9 +16,6 @@
 
 package android.app.admin;
 
-import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID;
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED;
-
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest.permission;
@@ -42,6 +39,7 @@
 import android.app.Activity;
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
@@ -62,6 +60,7 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.nfc.NfcAdapter;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -99,6 +98,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.net.NetworkUtilsInternal;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
@@ -133,6 +133,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 // TODO(b/172376923) - add CarDevicePolicyManager examples below (or remove reference to it).
 /**
@@ -1795,6 +1796,28 @@
             "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
 
     /**
+     * Broadcast action: sent when there is a location update on a device in lost mode. This
+     * broadcast is explicitly sent to the device policy controller app only.
+     *
+     * @see DevicePolicyManager#sendLostModeLocationUpdate
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_LOST_MODE_LOCATION_UPDATE =
+            "android.app.action.LOST_MODE_LOCATION_UPDATE";
+
+    /**
+     * Extra used with {@link #ACTION_LOST_MODE_LOCATION_UPDATE} to send the location of a device
+     * in lost mode. Value is {@code Location}.
+     *
+     * @see DevicePolicyManager#sendLostModeLocationUpdate
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_LOST_MODE_LOCATION =
+            "android.app.extra.LOST_MODE_LOCATION";
+
+    /**
      * The ComponentName of the administrator component.
      *
      * @see #ACTION_ADD_DEVICE_ADMIN
@@ -5824,6 +5847,56 @@
     }
 
     /**
+     * Send a lost mode location update to the admin. This API is limited to organization-owned
+     * devices, which includes devices with a device owner or devices with a profile owner on an
+     * organization-owned managed profile.
+     *
+     * <p>The caller must hold the
+     * {@link android.Manifest.permission#SEND_LOST_MODE_LOCATION_UPDATES} permission.
+     *
+     * <p> Not for use by third-party applications.
+     *
+     * @param executor The executor through which the callback should be invoked.
+     * @param callback A callback object that will inform the caller whether a lost mode location
+     *                 update was successfully sent
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SEND_LOST_MODE_LOCATION_UPDATES)
+    public void sendLostModeLocationUpdate(@NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> callback) {
+        throwIfParentInstance("sendLostModeLocationUpdate");
+        if (mService == null) {
+            executeCallback(AndroidFuture.completedFuture(false), executor, callback);
+            return;
+        }
+        try {
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
+            mService.sendLostModeLocationUpdate(future);
+            executeCallback(future, executor, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void executeCallback(AndroidFuture<Boolean> future,
+            @CallbackExecutor @NonNull Executor executor,
+            Consumer<Boolean> callback) {
+        future.whenComplete((result, error) -> executor.execute(() -> {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (error != null) {
+                    callback.accept(false);
+                } else {
+                    callback.accept(result);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }));
+    }
+
+    /**
      * Called by an application that is administering the device to set the
      * global proxy and exclusion list.
      * <p>
@@ -10019,14 +10092,17 @@
 
     /**
      * Gets the user a {@link #logoutUser(ComponentName)} call would switch to,
-     * or {@link UserHandle#USER_NULL} if the current user is not in a session.
+     * or {@code null} if the current user is not in a session.
      *
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
-    public @UserIdInt int getLogoutUserId() {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public @Nullable UserHandle getLogoutUser() {
+        // TODO(b/214336184): add CTS test
         try {
-            return mService.getLogoutUserId();
+            int userId = mService.getLogoutUserId();
+            return userId == UserHandle.USER_NULL ? null : UserHandle.of(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -10040,7 +10116,9 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public void clearLogoutUser() {
+        // TODO(b/214336184): add CTS test
         try {
             mService.clearLogoutUser();
         } catch (RemoteException re) {
@@ -10677,6 +10755,9 @@
      * On fully-managed devices this method is unsupported because all traffic is considered
      * work traffic.
      *
+     * <p> This method enables preferential network service with a default configuration.
+     * To fine-tune the configuration, use {@link #setPreferentialNetworkServiceConfig) instead.
+     *
      * <p>This method can only be called by the profile owner of a managed profile.
      * @param enabled whether preferential network service should be enabled.
      * @throws SecurityException if the caller is not the profile owner.
@@ -10715,6 +10796,56 @@
     }
 
     /**
+     * Sets preferential network configuration on the work profile.
+     * {@see PreferentialNetworkServiceConfig}
+     *
+     * An example of a supported preferential network service is the Enterprise
+     * slice on 5G networks.
+     *
+     * By default, preferential network service is disabled on the work profile on supported
+     * carriers and devices. Admins can explicitly enable it with this API.
+     * On fully-managed devices this method is unsupported because all traffic is considered
+     * work traffic.
+     *
+     * <p>This method can only be called by the profile owner of a managed profile.
+     * @param preferentialNetworkServiceConfig preferential network configuration.
+     * @throws SecurityException if the caller is not the profile owner.
+     **/
+    public void setPreferentialNetworkServiceConfig(
+            @NonNull PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        throwIfParentInstance("setPreferentialNetworkServiceConfig");
+        if (mService == null) {
+            return;
+        }
+        try {
+            mService.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfig);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get preferential network configuration
+     * {@see PreferentialNetworkServiceConfig}
+     *
+     * <p>This method can be called by the profile owner of a managed profile.
+     *
+     * @return preferential network configuration.
+     * @throws SecurityException if the caller is not the profile owner.
+     */
+    public @NonNull PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+        throwIfParentInstance("getPreferentialNetworkServiceConfig");
+        if (mService == null) {
+            return PreferentialNetworkServiceConfig.DEFAULT;
+        }
+        try {
+            return mService.getPreferentialNetworkServiceConfig();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * This method is mostly deprecated.
      * Most of the settings that still have an effect have dedicated setter methods or user
      * restrictions. See individual settings for details.
@@ -14733,17 +14864,17 @@
     /**
      * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if
      * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to
-     * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for
+     * {@link DevicePolicyResources.Drawables.Source#UNDEFINED}, it updates the drawable resource for
      * the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and
      * {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see
-     * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to
+     * {@link DevicePolicyResources.Drawables} and {@link DevicePolicyResources.Drawables.Style}) to
      * the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()},
      * meaning any system UI surface calling {@link #getDrawable}
      * with {@code drawableId} and {@code drawableStyle} will get the new resource after this API
      * is called.
      *
      * <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see
-     * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for
+     * {@link DevicePolicyResources.Drawables.Source}, it overrides any drawables that was set for
      * the same {@code drawableId} and {@code drawableStyle} for the provided source.
      *
      * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14779,10 +14910,10 @@
 
     /**
      * Removes all updated drawables for the list of {@code drawableIds} (see
-     * {@link DevicePolicyResources.Drawable} that was previously set by calling
+     * {@link DevicePolicyResources.Drawables} that was previously set by calling
      * {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided
-     * IDs with any {@link DevicePolicyResources.Drawable.Style} and any
-     * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from
+     * IDs with any {@link DevicePolicyResources.Drawables.Style} and any
+     * {@link DevicePolicyResources.Drawables.Source} will return the default drawable from
      * {@code defaultDrawableLoader}.
      *
      * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
@@ -14794,7 +14925,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)
-    public void resetDrawables(@NonNull int[] drawableIds) {
+    public void resetDrawables(@NonNull String[] drawableIds) {
         if (mService != null) {
             try {
                 mService.resetDrawables(drawableIds);
@@ -14806,19 +14937,19 @@
 
     /**
      * Returns the appropriate updated drawable for the {@code drawableId}
-     * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle}
-     * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using
+     * (see {@link DevicePolicyResources.Drawables}), with style {@code drawableStyle}
+     * (see {@link DevicePolicyResources.Drawables.Style}) if one was set using
      * {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}.
      *
      * <p>Also returns the drawable from {@code defaultDrawableLoader} if
-     * {@link DevicePolicyResources.Drawable#INVALID_ID} was passed.
+     * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
      * {@link NullPointerException} is thrown.
      *
      * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to
      * set a different value use
-     * {@link #getDrawableForDensity(int, int, int, Callable)}.
+     * {@link #getDrawableForDensity(String, String, int, Callable)}.
      *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
@@ -14833,16 +14964,18 @@
      */
     @NonNull
     public Drawable getDrawable(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
-        return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader);
+        return getDrawable(
+                drawableId, drawableStyle, Drawables.Source.UNDEFINED, defaultDrawableLoader);
     }
 
     /**
-     * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
-     * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which
-     * could result in returning a different drawable than {@link #getDrawable(int, int, Callable)}
+     * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
+     * a {@code drawableSource} (see {@link DevicePolicyResources.Drawables.Source}) which
+     * could result in returning a different drawable than
+     * {@link #getDrawable(String, String, Callable)}
      * if an override was set for that specific source.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14859,12 +14992,17 @@
      */
     @NonNull
     public Drawable getDrawable(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
+
+        Objects.requireNonNull(drawableId, "drawableId can't be null");
+        Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+        Objects.requireNonNull(drawableSource, "drawableSource can't be null");
         Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-        if (drawableId == INVALID_ID) {
+
+        if (Drawables.UNDEFINED.equals(drawableId)) {
             return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
         }
         if (mService != null) {
@@ -14892,7 +15030,7 @@
     }
 
     /**
-     * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts
+     * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14911,20 +15049,20 @@
      */
     @NonNull
     public Drawable getDrawableForDensity(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
             int density,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
         return getDrawableForDensity(
                 drawableId,
                 drawableStyle,
-                UNDEFINED,
+                Drawables.Source.UNDEFINED,
                 density,
                 defaultDrawableLoader);
     }
 
      /**
-     * Similar to {@link #getDrawable(int, int, int, Callable)}, but also accepts
+     * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts
      * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}.
      *
      * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a
@@ -14944,13 +15082,18 @@
      */
     @NonNull
     public Drawable getDrawableForDensity(
-            @DevicePolicyResources.UpdatableDrawableId int drawableId,
-            @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle,
-            @DevicePolicyResources.UpdatableDrawableSource int drawableSource,
+            @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId,
+            @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle,
+            @NonNull @DevicePolicyResources.UpdatableDrawableSource String drawableSource,
             int density,
             @NonNull Callable<Drawable> defaultDrawableLoader) {
+
+        Objects.requireNonNull(drawableId, "drawableId can't be null");
+        Objects.requireNonNull(drawableStyle, "drawableStyle can't be null");
+        Objects.requireNonNull(drawableSource, "drawableSource can't be null");
         Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
-        if (drawableId == INVALID_ID) {
+
+        if (Drawables.UNDEFINED.equals(drawableId)) {
             return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
         }
         if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 46e2cce..2ad2010 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -128,7 +128,6 @@
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY;
 import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY;
 
-import android.annotation.IntDef;
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -153,12 +152,12 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.INVALID_ID,
-            Drawable.WORK_PROFILE_ICON_BADGE,
-            Drawable.WORK_PROFILE_ICON,
-            Drawable.WORK_PROFILE_OFF_ICON,
-            Drawable.WORK_PROFILE_USER_ICON
+    @StringDef(value = {
+            Drawables.UNDEFINED,
+            Drawables.WORK_PROFILE_ICON_BADGE,
+            Drawables.WORK_PROFILE_ICON,
+            Drawables.WORK_PROFILE_OFF_ICON,
+            Drawables.WORK_PROFILE_USER_ICON
     })
     public @interface UpdatableDrawableId {}
 
@@ -169,10 +168,11 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.Style.SOLID_COLORED,
-            Drawable.Style.SOLID_NOT_COLORED,
-            Drawable.Style.OUTLINE,
+    @StringDef(value = {
+            Drawables.Style.SOLID_COLORED,
+            Drawables.Style.SOLID_NOT_COLORED,
+            Drawables.Style.OUTLINE,
+            Drawables.Style.DEFAULT
     })
     public @interface UpdatableDrawableStyle {}
 
@@ -182,14 +182,14 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {
-            Drawable.Source.UNDEFINED,
-            Drawable.Source.NOTIFICATION,
-            Drawable.Source.PROFILE_SWITCH_ANIMATION,
-            Drawable.Source.HOME_WIDGET,
-            Drawable.Source.LAUNCHER_OFF_BUTTON,
-            Drawable.Source.QUICK_SETTINGS,
-            Drawable.Source.STATUS_BAR
+    @StringDef(value = {
+            Drawables.Source.UNDEFINED,
+            Drawables.Source.NOTIFICATION,
+            Drawables.Source.PROFILE_SWITCH_ANIMATION,
+            Drawables.Source.HOME_WIDGET,
+            Drawables.Source.LAUNCHER_OFF_BUTTON,
+            Drawables.Source.QUICK_SETTINGS,
+            Drawables.Source.STATUS_BAR
     })
     public @interface UpdatableDrawableSource {}
 
@@ -265,44 +265,44 @@
     /**
      * Class containing the identifiers used to update device management-related system drawable.
      */
-    public static final class Drawable {
+    public static final class Drawables {
 
-        private Drawable() {
+        private Drawables() {
         }
 
         /**
          * An ID for any drawable that can't be updated.
          */
-        public static final int INVALID_ID = -1;
+        public static final String UNDEFINED = "UNDEFINED";
 
         /**
          * Specifically used to badge work profile app icons.
          */
-        public static final int WORK_PROFILE_ICON_BADGE = 0;
+        public static final String WORK_PROFILE_ICON_BADGE = "WORK_PROFILE_ICON_BADGE";
 
         /**
          * General purpose work profile icon (i.e. generic icon badging). For badging app icons
          * specifically, see {@link #WORK_PROFILE_ICON_BADGE}.
          */
-        public static final int WORK_PROFILE_ICON = 1;
+        public static final String WORK_PROFILE_ICON = "WORK_PROFILE_ICON";
 
         /**
          * General purpose icon representing the work profile off state.
          */
-        public static final int WORK_PROFILE_OFF_ICON = 2;
+        public static final String WORK_PROFILE_OFF_ICON = "WORK_PROFILE_OFF_ICON";
 
         /**
          * General purpose icon for the work profile user avatar.
          */
-        public static final int WORK_PROFILE_USER_ICON = 3;
+        public static final String WORK_PROFILE_USER_ICON = "WORK_PROFILE_USER_ICON";
 
         /**
          * @hide
          */
-        public static final Set<Integer> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
+        public static final Set<String> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet();
 
-        private static Set<Integer> buildDrawablesSet() {
-            Set<Integer> drawables = new HashSet<>();
+        private static Set<String> buildDrawablesSet() {
+            Set<String> drawables = new HashSet<>();
             drawables.add(WORK_PROFILE_ICON_BADGE);
             drawables.add(WORK_PROFILE_ICON);
             drawables.add(WORK_PROFILE_OFF_ICON);
@@ -323,48 +323,48 @@
              * A source identifier indicating that the updatable resource is used in a generic
              * undefined location.
              */
-            public static final int UNDEFINED = -1;
+            public static final String UNDEFINED = "UNDEFINED";
 
             /**
              * A source identifier indicating that the updatable drawable is used in notifications.
              */
-            public static final int NOTIFICATION = 0;
+            public static final String NOTIFICATION = "NOTIFICATION";
 
             /**
              * A source identifier indicating that the updatable drawable is used in a cross
              * profile switching animation.
              */
-            public static final int PROFILE_SWITCH_ANIMATION = 1;
+            public static final String PROFILE_SWITCH_ANIMATION = "PROFILE_SWITCH_ANIMATION";
 
             /**
              * A source identifier indicating that the updatable drawable is used in a work
              * profile home screen widget.
              */
-            public static final int HOME_WIDGET = 2;
+            public static final String HOME_WIDGET = "HOME_WIDGET";
 
             /**
              * A source identifier indicating that the updatable drawable is used in the launcher
              * turn off work button.
              */
-            public static final int LAUNCHER_OFF_BUTTON = 3;
+            public static final String LAUNCHER_OFF_BUTTON = "LAUNCHER_OFF_BUTTON";
 
             /**
              * A source identifier indicating that the updatable drawable is used in quick settings.
              */
-            public static final int QUICK_SETTINGS = 4;
+            public static final String QUICK_SETTINGS = "QUICK_SETTINGS";
 
             /**
              * A source identifier indicating that the updatable drawable is used in the status bar.
              */
-            public static final int STATUS_BAR = 5;
+            public static final String STATUS_BAR = "STATUS_BAR";
 
             /**
              * @hide
              */
-            public static final Set<Integer> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
+            public static final Set<String> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet();
 
-            private static Set<Integer> buildSourcesSet() {
-                Set<Integer> sources = new HashSet<>();
+            private static Set<String> buildSourcesSet() {
+                Set<String> sources = new HashSet<>();
                 sources.add(UNDEFINED);
                 sources.add(NOTIFICATION);
                 sources.add(PROFILE_SWITCH_ANIMATION);
@@ -390,31 +390,31 @@
              * A style identifier indicating that the updatable drawable should use the default
              * style.
              */
-            public static final int DEFAULT = -1;
+            public static final String DEFAULT = "DEFAULT";
 
             /**
              * A style identifier indicating that the updatable drawable has a solid color fill.
              */
-            public static final int SOLID_COLORED = 0;
+            public static final String SOLID_COLORED = "SOLID_COLORED";
 
             /**
              * A style identifier indicating that the updatable drawable has a solid non-colored
              * fill.
              */
-            public static final int SOLID_NOT_COLORED = 1;
+            public static final String SOLID_NOT_COLORED = "SOLID_NOT_COLORED";
 
             /**
              * A style identifier indicating that the updatable drawable is an outline.
              */
-            public static final int OUTLINE = 2;
+            public static final String OUTLINE = "OUTLINE";
 
             /**
              * @hide
              */
-            public static final Set<Integer> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
+            public static final Set<String> UPDATABLE_DRAWABLE_STYLES = buildStylesSet();
 
-            private static Set<Integer> buildStylesSet() {
-                Set<Integer> styles = new HashSet<>();
+            private static Set<String> buildStylesSet() {
+                Set<String> styles = new HashSet<>();
                 styles.add(DEFAULT);
                 styles.add(SOLID_COLORED);
                 styles.add(SOLID_NOT_COLORED);
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index f663c17..7525d54 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.admin.ParcelableGranteeMap;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
@@ -47,6 +48,7 @@
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.telephony.data.ApnSetting;
+import com.android.internal.infra.AndroidFuture;
 
 import java.util.List;
 
@@ -119,6 +121,8 @@
     FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
     boolean isFactoryResetProtectionPolicySupported();
 
+    void sendLostModeLocationUpdate(in AndroidFuture<boolean> future);
+
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
     void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
@@ -283,6 +287,10 @@
     void setPreferentialNetworkServiceEnabled(in boolean enabled);
     boolean isPreferentialNetworkServiceEnabled(int userHandle);
 
+    void setPreferentialNetworkServiceConfig(
+            in PreferentialNetworkServiceConfig preferentialNetworkServiceConfig);
+    PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig();
+
     void setLockTaskPackages(in ComponentName who, in String[] packages);
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
@@ -543,8 +551,8 @@
 
     List<UserHandle> listForegroundAffiliatedUsers();
     void setDrawables(in List<DevicePolicyDrawableResource> drawables);
-    void resetDrawables(in int[] drawableIds);
-    ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource);
+    void resetDrawables(in String[] drawableIds);
+    ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource);
 
     void setStrings(in List<DevicePolicyStringResource> strings);
     void resetStrings(in String[] stringIds);
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
new file mode 100644
index 0000000..6b6ee7d
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2022, 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.app.admin;
+
+parcelable PreferentialNetworkServiceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/admin/PreferentialNetworkServiceConfig.java b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
new file mode 100644
index 0000000..2849139
--- /dev/null
+++ b/core/java/android/app/admin/PreferentialNetworkServiceConfig.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 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.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Network configuration to be set for the user profile
+ * {@see DevicePolicyManager#setPreferentialNetworkServiceConfig}.
+ */
+public final class PreferentialNetworkServiceConfig implements Parcelable {
+    final boolean mIsEnabled;
+    final int mNetworkId;
+    final boolean mAllowFallbackToDefaultConnection;
+    final int[] mIncludedUids;
+    final int[] mExcludedUids;
+
+    /** @hide */
+    public static final PreferentialNetworkServiceConfig DEFAULT =
+            (new PreferentialNetworkServiceConfig.Builder()).build();
+
+    /**
+     * Preferential network identifier 1.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_1 = 1;
+
+    /**
+     * Preferential network identifier 2.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_2 = 2;
+
+    /**
+     * Preferential network identifier 3.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_3 = 3;
+
+    /**
+     * Preferential network identifier 4.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_4 = 4;
+
+    /**
+     * Preferential network identifier 5.
+     */
+    public static final int PREFERENTIAL_NETWORK_ID_5 = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PREFERENTIAL_NETWORK_ID_" }, value = {
+            PREFERENTIAL_NETWORK_ID_1,
+            PREFERENTIAL_NETWORK_ID_2,
+            PREFERENTIAL_NETWORK_ID_3,
+            PREFERENTIAL_NETWORK_ID_4,
+            PREFERENTIAL_NETWORK_ID_5,
+    })
+
+    public @interface PreferentialNetworkPreferenceId {
+    }
+
+    private PreferentialNetworkServiceConfig(boolean isEnabled,
+            boolean allowFallbackToDefaultConnection, int[] includedUids,
+            int[] excludedUids, @PreferentialNetworkPreferenceId int networkId) {
+        mIsEnabled = isEnabled;
+        mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+        mIncludedUids = includedUids;
+        mExcludedUids = excludedUids;
+        mNetworkId = networkId;
+    }
+
+    private PreferentialNetworkServiceConfig(Parcel in) {
+        mIsEnabled = in.readBoolean();
+        mAllowFallbackToDefaultConnection = in.readBoolean();
+        mNetworkId = in.readInt();
+        mIncludedUids = in.createIntArray();
+        mExcludedUids = in.createIntArray();
+    }
+
+    /**
+     * Is the preferential network enabled.
+     * @return true if enabled else false
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * is fallback to default network allowed. This boolean configures whether default connection
+     * (default internet or wifi) should be used or not if a preferential network service
+     * connection is not available.
+     * @return true if fallback is allowed, else false.
+     */
+    public boolean isFallbackToDefaultConnectionAllowed() {
+        return mAllowFallbackToDefaultConnection;
+    }
+
+    /**
+     * Get the array of uids that are applicable for the profile preference.
+     *
+     * {@see #getExcludedUids()}
+     * Included UIDs and Excluded UIDs can't both be non-empty.
+     * if both are empty, it means this request applies to all uids in the user profile.
+     * if included is not empty, then only included UIDs are applied.
+     * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+     * @return Array of uids applicable for the profile preference.
+     *      Empty array would mean that this request applies to all uids in the profile.
+     */
+    public @NonNull int[] getIncludedUids() {
+        return mIncludedUids;
+    }
+
+    /**
+     * Get the array of uids that are excluded for the profile preference.
+     *
+     * {@see #getIncludedUids()}
+     * Included UIDs and Excluded UIDs can't both be non-empty.
+     * if both are empty, it means this request applies to all uids in the user profile.
+     * if included is not empty, then only included UIDs are applied.
+     * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+     * @return Array of uids that are excluded for the profile preference.
+     *      Empty array would mean that this request applies to all uids in the profile.
+     */
+    public @NonNull int[] getExcludedUids() {
+        return mExcludedUids;
+    }
+
+    /**
+     * @return preference enterprise identifier.
+     * valid values starts from
+     * {@link #PREFERENTIAL_NETWORK_ID_1} to {@link #PREFERENTIAL_NETWORK_ID_5}.
+     * preference identifier is applicable only if preference network service is enabled
+     *
+     */
+    public @PreferentialNetworkPreferenceId int getNetworkId() {
+        return mNetworkId;
+    }
+
+    @Override
+    public String toString() {
+        return "PreferentialNetworkServiceConfig{"
+                + "mIsEnabled=" + isEnabled()
+                + "mAllowFallbackToDefaultConnection=" + isFallbackToDefaultConnectionAllowed()
+                + "mIncludedUids=" + mIncludedUids.toString()
+                + "mExcludedUids=" + mExcludedUids.toString()
+                + "mNetworkId=" + mNetworkId
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        final PreferentialNetworkServiceConfig that = (PreferentialNetworkServiceConfig) o;
+        return mIsEnabled == that.mIsEnabled
+                && mAllowFallbackToDefaultConnection == that.mAllowFallbackToDefaultConnection
+                && mNetworkId == that.mNetworkId
+                && Objects.equals(mIncludedUids, that.mIncludedUids)
+                && Objects.equals(mExcludedUids, that.mExcludedUids);
+    }
+
+    @Override
+    public int hashCode() {
+        return ((Objects.hashCode(mIsEnabled) * 17)
+                + (Objects.hashCode(mAllowFallbackToDefaultConnection) * 19)
+                + (Objects.hashCode(mIncludedUids) * 23)
+                + (Objects.hashCode(mExcludedUids) * 29)
+                + mNetworkId * 31);
+    }
+
+    /**
+     * Builder used to create {@link PreferentialNetworkServiceConfig} objects.
+     * Specify the preferred Network preference
+     */
+    public static final class Builder {
+        boolean mIsEnabled = false;
+        int mNetworkId = 0;
+        boolean mAllowFallbackToDefaultConnection = true;
+        int[] mIncludedUids = new int[0];
+        int[] mExcludedUids = new int[0];
+
+        /**
+         * Constructs an empty Builder with preferential network disabled by default.
+         */
+        public Builder() {}
+
+        /**
+         * Set the preferential network service enabled state.
+         * Default value is false.
+         * @param isEnabled  the desired network preference to use, true to enable else false
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setEnabled(boolean isEnabled) {
+            mIsEnabled = isEnabled;
+            return this;
+        }
+
+        /**
+         * Set whether the default connection should be used as fallback.
+         * This boolean configures whether the default connection (default internet or wifi)
+         * should be used if a preferential network service connection is not available.
+         * Default value is true
+         * @param allowFallbackToDefaultConnection  true if fallback is allowed else false
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public PreferentialNetworkServiceConfig.Builder setFallbackToDefaultConnectionAllowed(
+                boolean allowFallbackToDefaultConnection) {
+            mAllowFallbackToDefaultConnection = allowFallbackToDefaultConnection;
+            return this;
+        }
+
+        /**
+         * Set the array of uids whose network access will go through this preferential
+         * network service.
+         * {@see #setExcludedUids(int[])}
+         * Included UIDs and Excluded UIDs can't both be non-empty.
+         * if both are empty, it means this request applies to all uids in the user profile.
+         * if included is not empty, then only included UIDs are applied.
+         * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+         * @param uids  array of included uids
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setIncludedUids(
+                @NonNull int[] uids) {
+            Objects.requireNonNull(uids);
+            mIncludedUids = uids;
+            return this;
+        }
+
+        /**
+         * Set the array of uids who are not allowed through this preferential
+         * network service.
+         * {@see #setIncludedUids(int[])}
+         * Included UIDs and Excluded UIDs can't both be non-empty.
+         * if both are empty, it means this request applies to all uids in the user profile.
+         * if included is not empty, then only included UIDs are applied.
+         * if excluded is not empty, then it is all uids in the user profile except these UIDs.
+         * @param uids  array of excluded uids
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setExcludedUids(
+                @NonNull int[] uids) {
+            Objects.requireNonNull(uids);
+            mExcludedUids = uids;
+            return this;
+        }
+
+        /**
+         * Returns an instance of {@link PreferentialNetworkServiceConfig} created from the
+         * fields set on this builder.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig build() {
+            if (mIncludedUids.length > 0 && mExcludedUids.length > 0) {
+                throw new IllegalStateException("Both includedUids and excludedUids "
+                        + "cannot be nonempty");
+            }
+            return new PreferentialNetworkServiceConfig(mIsEnabled,
+                    mAllowFallbackToDefaultConnection, mIncludedUids, mExcludedUids, mNetworkId);
+        }
+
+        /**
+         * Set the preferential network identifier.
+         * Valid values starts from {@link #PREFERENTIAL_NETWORK_ID_1} to
+         * {@link #PREFERENTIAL_NETWORK_ID_5}.
+         * preference identifier is applicable only if preferential network service is enabled.
+         * @param preferenceId  preference Id
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public PreferentialNetworkServiceConfig.Builder setNetworkId(
+                @PreferentialNetworkPreferenceId int preferenceId) {
+            if ((preferenceId < PREFERENTIAL_NETWORK_ID_1)
+                    || (preferenceId > PREFERENTIAL_NETWORK_ID_5)) {
+                throw new IllegalArgumentException("Invalid preference identifier");
+            }
+            mNetworkId = preferenceId;
+            return this;
+        }
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mAllowFallbackToDefaultConnection);
+        dest.writeInt(mNetworkId);
+        dest.writeIntArray(mIncludedUids);
+        dest.writeIntArray(mExcludedUids);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<PreferentialNetworkServiceConfig> CREATOR =
+            new Creator<PreferentialNetworkServiceConfig>() {
+                @Override
+                public PreferentialNetworkServiceConfig[] newArray(int size) {
+                    return new PreferentialNetworkServiceConfig[size];
+                }
+
+                @Override
+                public PreferentialNetworkServiceConfig createFromParcel(
+                        @NonNull android.os.Parcel in) {
+                    return new PreferentialNetworkServiceConfig(in);
+                }
+            };
+}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
new file mode 100644
index 0000000..53af4c5
--- /dev/null
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.companion.virtual;
+
+import android.content.ComponentName;
+
+/**
+ * Interface to listen for activity changes in a virtual device.
+ *
+ * @hide
+ */
+interface IVirtualDeviceActivityListener {
+
+    /**
+     * Called when the top activity is changed.
+     *
+     * @param displayId The display ID on which the activity change happened.
+     * @param topActivity The component name of the top activity.
+     */
+    void onTopActivityChanged(int displayId, in ComponentName topActivity);
+
+    /**
+     * Called when the display becomes empty (e.g. if the user hits back on the last
+     * activity of the root task).
+     *
+     * @param displayId The display ID that became empty.
+     */
+    void onDisplayEmpty(int displayId);
+}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index d80bee6..b7f826a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -17,6 +17,7 @@
 package android.companion.virtual;
 
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 
 /**
@@ -39,5 +40,5 @@
      */
     IVirtualDevice createVirtualDevice(
             in IBinder token, String packageName, int associationId,
-            in VirtualDeviceParams params);
+            in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c723468..64f16ac 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -25,6 +25,7 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager;
@@ -40,6 +41,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.util.ArrayMap;
 import android.view.Surface;
 
 import java.util.concurrent.Executor;
@@ -87,9 +89,7 @@
             int associationId,
             @NonNull VirtualDeviceParams params) {
         try {
-            IVirtualDevice virtualDevice = mService.createVirtualDevice(
-                    new Binder(), mContext.getPackageName(), associationId, params);
-            return new VirtualDevice(mContext, virtualDevice);
+            return new VirtualDevice(mService, mContext, associationId, params);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -106,10 +106,49 @@
 
         private final Context mContext;
         private final IVirtualDevice mVirtualDevice;
+        private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners =
+                new ArrayMap<>();
+        private final IVirtualDeviceActivityListener mActivityListenerBinder =
+                new IVirtualDeviceActivityListener.Stub() {
 
-        private VirtualDevice(Context context, IVirtualDevice virtualDevice) {
+                    @Override
+                    public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            for (int i = 0; i < mActivityListeners.size(); i++) {
+                                mActivityListeners.valueAt(i)
+                                        .onTopActivityChanged(displayId, topActivity);
+                            }
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
+
+                    @Override
+                    public void onDisplayEmpty(int displayId) {
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            for (int i = 0; i < mActivityListeners.size(); i++) {
+                                mActivityListeners.valueAt(i).onDisplayEmpty(displayId);
+                            }
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
+                };
+
+        private VirtualDevice(
+                IVirtualDeviceManager service,
+                Context context,
+                int associationId,
+                VirtualDeviceParams params) throws RemoteException {
             mContext = context.getApplicationContext();
-            mVirtualDevice = virtualDevice;
+            mVirtualDevice = service.createVirtualDevice(
+                    new Binder(),
+                    mContext.getPackageName(),
+                    associationId,
+                    params,
+                    mActivityListenerBinder);
         }
 
         /**
@@ -137,7 +176,7 @@
                 mVirtualDevice.launchPendingIntent(
                         displayId,
                         pendingIntent,
-                        new ResultReceiver(new Handler(Looper.myLooper())) {
+                        new ResultReceiver(new Handler(Looper.getMainLooper())) {
                             @Override
                             protected void onReceiveResult(int resultCode, Bundle resultData) {
                                 super.onReceiveResult(resultCode, resultData);
@@ -323,6 +362,47 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * Adds an activity listener to listen for events such as top activity change or virtual
+         * display task stack became empty.
+         *
+         * @param listener The listener to add.
+         * @see #removeActivityListener(ActivityListener)
+         * @hide
+         */
+        // TODO(b/194949534): Unhide this API
+        public void addActivityListener(@NonNull ActivityListener listener) {
+            addActivityListener(listener, mContext.getMainExecutor());
+        }
+
+        /**
+         * Adds an activity listener to listen for events such as top activity change or virtual
+         * display task stack became empty.
+         *
+         * @param listener The listener to add.
+         * @param executor The executor where the callback is executed on.
+         * @see #removeActivityListener(ActivityListener)
+         * @hide
+         */
+        // TODO(b/194949534): Unhide this API
+        public void addActivityListener(
+                @NonNull ActivityListener listener, @NonNull Executor executor) {
+            mActivityListeners.put(listener, new ActivityListenerDelegate(listener, executor));
+        }
+
+        /**
+         * Removes an activity listener previously added with
+         * {@link #addActivityListener}.
+         *
+         * @param listener The listener to remove.
+         * @see #addActivityListener(ActivityListener, Executor)
+         * @hide
+         */
+        // TODO(b/194949534): Unhide this API
+        public void removeActivityListener(@NonNull ActivityListener listener) {
+            mActivityListeners.remove(listener);
+        }
     }
 
     /**
@@ -342,4 +422,50 @@
          */
         void onLaunchFailed();
     }
+
+    /**
+     * Listener for activity changes in this virtual device.
+     *
+     * @hide
+     */
+    // TODO(b/194949534): Unhide this API
+    public interface ActivityListener {
+
+        /**
+         * Called when the top activity is changed.
+         *
+         * @param displayId The display ID on which the activity change happened.
+         * @param topActivity The component name of the top activity.
+         */
+        void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity);
+
+        /**
+         * Called when the display becomes empty (e.g. if the user hits back on the last
+         * activity of the root task).
+         *
+         * @param displayId The display ID that became empty.
+         */
+        void onDisplayEmpty(int displayId);
+    }
+
+    /**
+     * A wrapper for {@link ActivityListener} that executes callbacks on the given executor.
+     */
+    private static class ActivityListenerDelegate {
+        @NonNull private final ActivityListener mActivityListener;
+        @NonNull private final Executor mExecutor;
+
+        ActivityListenerDelegate(@NonNull ActivityListener listener, @NonNull Executor executor) {
+            mActivityListener = listener;
+            mExecutor = executor;
+        }
+
+        public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+            mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity));
+        }
+
+        public void onDisplayEmpty(int displayId) {
+            mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
+        }
+    }
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b4f2302..5584f45 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Process.myUserHandle;
 import static android.os.Trace.TRACE_TAG_DATABASE;
 
 import android.annotation.NonNull;
@@ -738,7 +739,7 @@
     }
 
     boolean checkUser(int pid, int uid, Context context) {
-        int callingUserId = UserHandle.getUserId(uid);
+        final int callingUserId = UserHandle.getUserId(uid);
 
         if (callingUserId == context.getUserId() || mSingleUser) {
             return true;
@@ -765,8 +766,8 @@
                 if (um != null && um.isCloneProfile()) {
                     UserHandle parent = um.getProfileParent(callingUser);
 
-                    if (parent != null && parent.equals(context.getUser())) {
-                        mUsersRedirectedToOwner.put(callingUser.getIdentifier(), true);
+                    if (parent != null && parent.equals(myUserHandle())) {
+                        mUsersRedirectedToOwner.put(callingUserId, true);
                         return true;
                     }
                 }
@@ -774,7 +775,7 @@
                 // ignore
             }
 
-            mUsersRedirectedToOwner.put(UserHandle.getUserId(uid), false);
+            mUsersRedirectedToOwner.put(callingUserId, false);
             return false;
         }
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6ae768a..98ced6d 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -24,6 +24,9 @@
 import android.annotation.UiContext;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -47,12 +50,16 @@
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.view.autofill.AutofillManager.AutofillClient;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -66,6 +73,31 @@
     @UnsupportedAppUsage
     Context mBase;
 
+    /**
+     * After {@link Build.VERSION_CODES#TIRAMISU},
+     * {@link #registerComponentCallbacks(ComponentCallbacks)} will delegate to
+     * {@link #getBaseContext()} instead of {@link #getApplicationContext()}.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @VisibleForTesting
+    static final long COMPONENT_CALLBACK_ON_WRAPPER = 193247900L;
+
+    /**
+     * A list to store {@link ComponentCallbacks} which
+     * passes to {@link #registerComponentCallbacks(ComponentCallbacks)} before
+     * {@link #attachBaseContext(Context)}.
+     * It is to provide compatibility behavior for Application targeted prior to
+     * {@link Build.VERSION_CODES#TIRAMISU}.
+     *
+     * @hide
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    public List<ComponentCallbacks> mCallbacksRegisteredToSuper;
+
+    private final Object mLock = new Object();
+
     public ContextWrapper(Context base) {
         mBase = base;
     }
@@ -1301,4 +1333,77 @@
         }
         return mBase.isConfigurationContext();
     }
+
+    /**
+     * Add a new {@link ComponentCallbacks} to the base application of the
+     * Context, which will be called at the same times as the ComponentCallbacks
+     * methods of activities and other components are called. Note that you
+     * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
+     * appropriate in the future; this will not be removed for you.
+     * <p>
+     * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be registered
+     * to {@link #getBaseContext() the base Context}, and can be only used after
+     * {@link #attachBaseContext(Context)}. Users can still call to
+     * {@code getApplicationContext().registerComponentCallbacks(ComponentCallbacks)} to add
+     * {@link ComponentCallbacks} to the base application.
+     *
+     * @param callback The interface to call.  This can be either a
+     * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+     * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+     */
+    @Override
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        if (mBase != null) {
+            mBase.registerComponentCallbacks(callback);
+        } else if (!CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+            super.registerComponentCallbacks(callback);
+            synchronized (mLock) {
+                // Also register ComponentCallbacks to ContextWrapper, so we can find the correct
+                // Context to unregister it for compatibility.
+                if (mCallbacksRegisteredToSuper == null) {
+                    mCallbacksRegisteredToSuper = new ArrayList<>();
+                }
+                mCallbacksRegisteredToSuper.add(callback);
+            }
+        } else {
+            // Throw exception for Application targeting T+
+            throw new IllegalStateException("ComponentCallbacks must be registered after "
+                    + "this ContextWrapper is attached to a base Context.");
+        }
+    }
+
+    /**
+     * Remove a {@link ComponentCallbacks} object that was previously registered
+     * with {@link #registerComponentCallbacks(ComponentCallbacks)}.
+     * <p>
+     * After {@link Build.VERSION_CODES#TIRAMISU}, the {@link ComponentCallbacks} will be
+     * unregistered to {@link #getBaseContext() the base Context}, and can be only used after
+     * {@link #attachBaseContext(Context)}
+     * </p>
+     *
+     * @param callback The interface to call.  This can be either a
+     * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
+     * @throws IllegalStateException if this method calls before {@link #attachBaseContext(Context)}
+     */
+    @Override
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        // It usually means the ComponentCallbacks is registered before this ContextWrapper attaches
+        // to a base Context and Application is targeting prior to S-v2. We should unregister the
+        // ComponentCallbacks to the Application Context instead to prevent leak.
+        synchronized (mLock) {
+            if (mCallbacksRegisteredToSuper != null
+                    && mCallbacksRegisteredToSuper.contains(callback)) {
+                super.unregisterComponentCallbacks(callback);
+                mCallbacksRegisteredToSuper.remove(callback);
+            } else if (mBase != null) {
+                mBase.unregisterComponentCallbacks(callback);
+            } else if (CompatChanges.isChangeEnabled(COMPONENT_CALLBACK_ON_WRAPPER)) {
+                // Throw exception for Application that is targeting S-v2+
+                throw new IllegalStateException("ComponentCallbacks must be unregistered after "
+                        + "this ContextWrapper is attached to a base Context.");
+            }
+        }
+        // Do nothing if the callback hasn't been registered to Application Context by
+        // super.unregisterComponentCallbacks() for Application that is targeting prior to T.
+    }
 }
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index 7f1d0d1..5bb845d 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -32,6 +32,9 @@
         },
         {
           "include-filter": "android.content.ComponentCallbacksControllerTest"
+        },
+        {
+          "include-filter": "android.content.ContextWrapperTest"
         }
       ],
       "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java", "(/|^)ComponentCallbacksController.java"]
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1021d3e..5b0c275 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -329,7 +329,8 @@
          * @hide
          */
         public Bundle toBundle(Bundle outBundle) {
-            final Bundle b = outBundle == null ? new Bundle() : outBundle;
+            final Bundle b = outBundle == null || outBundle == Bundle.EMPTY
+                    ? new Bundle() : outBundle;
             if (mType == TYPE_BOOLEAN) {
                 b.putBoolean(mName, mBooleanValue);
             } else if (mType == TYPE_FLOAT) {
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index d5498a0..165cae8 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,6 +78,7 @@
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
+    private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     private static final String TAG_APPLICATION = "application";
     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_PROFILEABLE = "profileable";
@@ -101,7 +102,7 @@
     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(input, packageFile, flags);
+            return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
         } else {
             return parseMonolithicPackageLite(input, packageFile, flags);
         }
@@ -134,21 +135,44 @@
 
     /**
      * Parse lightweight details about a directory of APKs.
+     *
+     * @param packageDirOrApk is the folder that contains split apks for a regular app or the
+     *                        framework-res.apk for framwork-res splits (in which case the
+     *                        splits come in the <code>frameworkSplits</code> parameter)
      */
     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
-            File packageDir, int flags) {
-        final File[] files = packageDir.listFiles();
-        if (ArrayUtils.isEmpty(files)) {
-            return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                    "No packages found in split");
+            File packageDirOrApk, List<File> frameworkSplits, int flags) {
+        final File[] files;
+        final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
+        if (parsingFrameworkSplits) {
+            if (ArrayUtils.isEmpty(frameworkSplits)) {
+                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                        "No packages found in split");
+            }
+            files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
+            // we also want to process the base apk so add it to the array
+            files[files.length - 1] = packageDirOrApk;
+        } else {
+            files = packageDirOrApk.listFiles();
+            if (ArrayUtils.isEmpty(files)) {
+                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                        "No packages found in split");
+            }
+            // Apk directory is directly nested under the current directory
+            if (files.length == 1 && files[0].isDirectory()) {
+                return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
+            }
         }
-        // Apk directory is directly nested under the current directory
-        if (files.length == 1 && files[0].isDirectory()) {
-            return parseClusterPackageLite(input, files[0], flags);
+
+        if (parsingFrameworkSplits) {
+            // disable the flag for checking the certificates of the splits. We know they
+            // won't match, but we rely on the mainline apex to be safe if it was installed
+            flags = flags & ~PARSE_COLLECT_CERTIFICATES;
         }
 
         String packageName = null;
         int versionCode = 0;
+        ApkLite baseApk = null;
 
         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
@@ -161,6 +185,10 @@
                     }
 
                     final ApkLite lite = result.getResult();
+                    if (parsingFrameworkSplits && file == files[files.length - 1]) {
+                        baseApk = lite;
+                        break;
+                    }
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
@@ -172,7 +200,8 @@
                                     "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        if (versionCode != lite.getVersionCode()) {
+                        // we allow version codes that do not match for framework splits
+                        if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                     "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
@@ -187,12 +216,15 @@
                     }
                 }
             }
+            // baseApk is set in the last iteration of the for each loop when we are parsing
+            // frameworkRes splits or needs to be done now otherwise
+            if (!parsingFrameworkSplits) {
+                baseApk = apks.remove(null);
+            }
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
-
-        final ApkLite baseApk = apks.remove(null);
-        return composePackageLiteFromApks(input, packageDir, baseApk, apks);
+        return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
     }
 
     /**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2ac194b..0304815 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -50,6 +50,10 @@
     // Reports whether the hardware supports the given keys; returns true if successful
     boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists);
 
+    // Returns the keyCode produced when pressing the key at the specified location, given the
+    // active keyboard layout.
+    int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode);
+
     // Temporarily changes the pointer speed.
     void tryPointerSpeed(int speed);
 
diff --git a/core/java/android/hardware/input/InputDeviceIdentifier.java b/core/java/android/hardware/input/InputDeviceIdentifier.java
index c673e7a..a5b9a2a 100644
--- a/core/java/android/hardware/input/InputDeviceIdentifier.java
+++ b/core/java/android/hardware/input/InputDeviceIdentifier.java
@@ -16,7 +16,9 @@
 
 package android.hardware.input;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -28,12 +30,13 @@
  *
  * @hide
  */
+@TestApi
 public final class InputDeviceIdentifier implements Parcelable {
     private final String mDescriptor;
     private final int mVendorId;
     private final int mProductId;
 
-    public InputDeviceIdentifier(String descriptor, int vendorId, int productId) {
+    public InputDeviceIdentifier(@NonNull String descriptor, int vendorId, int productId) {
         this.mDescriptor = descriptor;
         this.mVendorId = vendorId;
         this.mProductId = productId;
@@ -51,12 +54,13 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mDescriptor);
         dest.writeInt(mVendorId);
         dest.writeInt(mProductId);
     }
 
+    @NonNull
     public String getDescriptor() {
         return mDescriptor;
     }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index ef349a9..cbc8373 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -58,6 +58,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.VerifiedInputEvent;
@@ -637,6 +638,30 @@
     }
 
     /**
+     * Returns the descriptors of all supported keyboard layouts appropriate for the specified
+     * input device.
+     * <p>
+     * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
+     * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+     * </p>
+     *
+     * @param device The input device to query.
+     * @return The ids of all keyboard layouts which are supported by the specified input device.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
+        KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+        List<String> res = new ArrayList<>();
+        for (KeyboardLayout kl : layouts) {
+            res.add(kl.getDescriptor());
+        }
+        return res;
+    }
+
+    /**
      * Gets information about all supported keyboard layouts appropriate
      * for a specific input device.
      * <p>
@@ -650,7 +675,9 @@
      *
      * @hide
      */
-    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+    @NonNull
+    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
+            @NonNull InputDeviceIdentifier identifier) {
         try {
             return mIm.getKeyboardLayoutsForInputDevice(identifier);
         } catch (RemoteException ex) {
@@ -680,15 +707,17 @@
     }
 
     /**
-     * Gets the current keyboard layout descriptor for the specified input
-     * device.
+     * Gets the current keyboard layout descriptor for the specified input device.
      *
      * @param identifier Identifier for the input device
-     * @return The keyboard layout descriptor, or null if no keyboard layout has
-     *         been set.
+     * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
+     *
      * @hide
      */
-    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+    @TestApi
+    @Nullable
+    public String getCurrentKeyboardLayoutForInputDevice(
+            @NonNull InputDeviceIdentifier identifier) {
         try {
             return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
         } catch (RemoteException ex) {
@@ -697,20 +726,21 @@
     }
 
     /**
-     * Sets the current keyboard layout descriptor for the specified input
-     * device.
+     * Sets the current keyboard layout descriptor for the specified input device.
      * <p>
-     * This method may have the side-effect of causing the input device in
-     * question to be reconfigured.
+     * This method may have the side-effect of causing the input device in question to be
+     * reconfigured.
      * </p>
      *
      * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use,
-     *            must not be null.
+     * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+     *
      * @hide
      */
-    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
+    @TestApi
+    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+    public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+            @NonNull String keyboardLayoutDescriptor) {
         if (identifier == null) {
             throw new IllegalArgumentException("identifier must not be null");
         }
@@ -727,11 +757,11 @@
     }
 
     /**
-     * Gets all keyboard layout descriptors that are enabled for the specified
-     * input device.
+     * Gets all keyboard layout descriptors that are enabled for the specified input device.
      *
      * @param identifier The identifier for the input device.
      * @return The keyboard layout descriptors.
+     *
      * @hide
      */
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
@@ -749,15 +779,16 @@
     /**
      * Adds the keyboard layout descriptor for the specified input device.
      * <p>
-     * This method may have the side-effect of causing the input device in
-     * question to be reconfigured.
+     * This method may have the side-effect of causing the input device in question to be
+     * reconfigured.
      * </p>
      *
      * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
-     *            add.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
+     *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
         if (identifier == null) {
@@ -777,17 +808,19 @@
     /**
      * Removes the keyboard layout descriptor for the specified input device.
      * <p>
-     * This method may have the side-effect of causing the input device in
-     * question to be reconfigured.
+     * This method may have the side-effect of causing the input device in question to be
+     * reconfigured.
      * </p>
      *
      * @param identifier The identifier for the input device.
-     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to
-     *            remove.
+     * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
+     *
      * @hide
      */
-    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            String keyboardLayoutDescriptor) {
+    @TestApi
+    @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+    public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+            @NonNull String keyboardLayoutDescriptor) {
         if (identifier == null) {
             throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
         }
@@ -1044,6 +1077,27 @@
         return ret;
     }
 
+    /**
+     * Gets the key code produced by the specified location on a US keyboard layout.
+     * Key code as defined in {@link android.view.KeyEvent}.
+     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
+     * which can alter their key mapping using country specific keyboard layouts.
+     *
+     * @param deviceId The input device id.
+     * @param locationKeyCode The location of a key on a US keyboard layout.
+     * @return The key code produced when pressing the key at the specified location, given the
+     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
+     *         mapping could not be determined, or if an error occurred.
+     * @hide
+     */
+    public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
+        try {
+            return mIm.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
 
     /**
      * Injects an input event into the event system on behalf of an application.
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index f866a2e..ad6c12b 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -90,6 +90,12 @@
     public abstract PointF getCursorPosition();
 
     /**
+     * Sets the pointer acceleration.
+     * See {@code frameworks/native/include/input/VelocityControl.h#VelocityControlParameters}.
+     */
+    public abstract void setPointerAcceleration(float acceleration);
+
+    /**
      * Sets the eligibility of windows on a given display for pointer capture. If a display is
      * marked ineligible, requests to enable pointer capture for windows on that display will be
      * ignored.
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 1d0837e..459dab1 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -139,6 +139,9 @@
     /* Set USB data on or off */
     boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback);
 
+    /* Enable USB data when disabled due to docking event  */
+    void enableUsbDataWhileDocked(in String portId, int operationId, in IUsbOperationInternal callback);
+
     /* Gets the USB Hal Version. */
     int getUsbHalVersion();
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index eb3e84d..f0e040e 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1359,6 +1359,35 @@
     }
 
     /**
+     * Should only be called by {@link UsbPort#enableUsbDataWhileDocked}.
+     * <p>
+     * Enables or disables USB data when disabled due to docking event.
+     *
+     * @param port USB port for which USB data needs to be enabled.
+     * @param operationId operationId for the request.
+     * @param callback callback object to be invoked when the operation is complete.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    void enableUsbDataWhileDocked(@NonNull UsbPort port, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(port, "enableUsbDataWhileDocked: port must not be null. opId:"
+                + operationId);
+        try {
+            mService.enableUsbDataWhileDocked(port.getId(), operationId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "enableUsbDataWhileDocked: failed. opId:" + operationId, e);
+            try {
+                callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL);
+            } catch (RemoteException r) {
+                Log.e(TAG, "enableUsbDataWhileDocked: failed to call onOperationComplete. opId:"
+                        + operationId, r);
+            }
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Sets the component that will handle USB device connection.
      * <p>
      * Setting component allows to specify external USB host manager to handle use cases, where
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index e908c24..bef4dea 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -33,9 +33,19 @@
 import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
 import static android.hardware.usb.UsbPortStatus.MODE_NONE;
 import static android.hardware.usb.UsbPortStatus.MODE_UFP;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_DISCONNECTED;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_CONNECTED;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_ENABLED;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_OVERHEAT;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_CONTAMINANT;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DOCK;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_DEBUG;
 
 import android.Manifest;
 import android.annotation.CheckResult;
@@ -154,6 +164,48 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface EnableLimitPowerTransferStatus{}
 
+    /**
+     * The {@link #enableUsbDataWhileDocked} request was successfully completed.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS = 0;
+
+    /**
+     * The {@link #enableUsbDataWhileDocked} request failed due to internal error.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL = 1;
+
+    /**
+     * The {@link #enableUsbDataWhileDocked} request failed as it's not supported.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED = 2;
+
+    /**
+     * The {@link #enableUsbDataWhileDocked} request failed as port id mismatched.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH = 3;
+
+    /**
+     * The {@link #enableUsbDataWhileDocked} request failed as data is still enabled.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED = 4;
+
+    /**
+     * The {@link #enableUsbDataWhileDocked} request failed due to other reasons.
+     */
+    public static final int ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER = 5;
+
+    /** @hide */
+    @IntDef(prefix = { "ENABLE_USB_DATA_WHILE_DOCKED_" }, value = {
+            ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS,
+            ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL,
+            ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED,
+            ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH,
+            ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED,
+            ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface EnableUsbDataWhileDockedStatus{}
+
     /** @hide */
     public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes,
             int supportedContaminantProtectionModes,
@@ -308,6 +360,51 @@
     }
 
     /**
+     * Enables Usb data when disabled due to {@link UsbPort#USB_DATA_STATUS_DISABLED_DOCK}
+     *
+     * @return {@link #ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS} when request completes successfully or
+     *         {@link #ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL} when request fails due to
+     *         internal error or
+     *         {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED} when not supported or
+     *         {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH} when request fails due to
+     *         port id mismatch or
+     *         {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED} when request fails as data
+     *         is still enabled or
+     *         {@link ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER} when fails due to other reasons.
+     */
+    @CheckResult
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public @EnableUsbDataWhileDockedStatus int enableUsbDataWhileDocked() {
+        // UID is added To minimize operationID overlap between two different packages.
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
+        Log.i(TAG, "enableUsbData opId:" + operationId
+                + " callingUid:" + Binder.getCallingUid());
+        UsbPortStatus portStatus = getStatus();
+        if (portStatus != null &&
+                !usbDataStatusToString(portStatus.getUsbDataStatus()).contains("disabled-dock")) {
+            return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_DATA_ENABLED;
+        }
+
+        UsbOperationInternal opCallback =
+                new UsbOperationInternal(operationId, mId);
+        mUsbManager.enableUsbDataWhileDocked(this, operationId, opCallback);
+                opCallback.waitForOperationComplete();
+        int result = opCallback.getStatus();
+        switch (result) {
+            case USB_OPERATION_SUCCESS:
+                return ENABLE_USB_DATA_WHILE_DOCKED_SUCCESS;
+            case USB_OPERATION_ERROR_INTERNAL:
+                return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_INTERNAL;
+            case USB_OPERATION_ERROR_NOT_SUPPORTED:
+                return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_NOT_SUPPORTED;
+            case USB_OPERATION_ERROR_PORT_MISMATCH:
+                return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_PORT_MISMATCH;
+            default:
+                return ENABLE_USB_DATA_WHILE_DOCKED_ERROR_OTHER;
+        }
+    }
+
+    /**
      * Limits power transfer In and out of the port.
      * <p>
      * Disables charging and limits sourcing power(when permitted by the USB spec) until
@@ -446,6 +543,57 @@
     }
 
     /** @hide */
+    public static String usbDataStatusToString(int usbDataStatus) {
+        switch (usbDataStatus) {
+            case USB_DATA_STATUS_UNKNOWN:
+                return "unknown";
+            case USB_DATA_STATUS_ENABLED:
+                return "enabled";
+            case USB_DATA_STATUS_DISABLED_OVERHEAT:
+                return "disabled-overheat";
+            case USB_DATA_STATUS_DISABLED_CONTAMINANT:
+                return "disabled-contaminant";
+            case USB_DATA_STATUS_DISABLED_DOCK:
+                return "disabled-dock";
+            case USB_DATA_STATUS_DISABLED_FORCE:
+                return "disabled-force";
+            case USB_DATA_STATUS_DISABLED_DEBUG:
+                return "disabled-debug";
+            default:
+                return Integer.toString(usbDataStatus);
+        }
+    }
+
+    /** @hide */
+    public static String usbDataStatusToString(int[] usbDataStatus) {
+        StringBuilder modeString = new StringBuilder();
+        if (usbDataStatus == null) {
+            return "unknown";
+        }
+        for (int i = 0; i < usbDataStatus.length; i++) {
+            modeString.append(usbDataStatusToString(usbDataStatus[i]));
+            if (i < usbDataStatus.length - 1) {
+                modeString.append(", ");
+            }
+        }
+        return modeString.toString();
+    }
+
+    /** @hide */
+    public static String powerBrickStatusToString(int powerBrickStatus) {
+        switch (powerBrickStatus) {
+            case POWER_BRICK_STATUS_UNKNOWN:
+                return "unknown";
+            case POWER_BRICK_STATUS_CONNECTED:
+                return "connected";
+            case POWER_BRICK_STATUS_DISCONNECTED:
+                return "disconnected";
+            default:
+                return Integer.toString(powerBrickStatus);
+        }
+    }
+
+    /** @hide */
     public static String roleCombinationsToString(int combo) {
         StringBuilder result = new StringBuilder();
         result.append("[");
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 934c506..d1f4246 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,8 +43,9 @@
     private final int mSupportedRoleCombinations;
     private final @ContaminantProtectionStatus int mContaminantProtectionStatus;
     private final @ContaminantDetectionStatus int mContaminantDetectionStatus;
-    private final boolean mUsbDataEnabled;
     private final boolean mPowerTransferLimited;
+    private final @UsbDataStatus int[] mUsbDataStatus;
+    private final @PowerBrickStatus int mPowerBrickStatus;
 
     /**
      * Power role: This USB port does not have a power role.
@@ -193,6 +195,57 @@
      */
     public static final int CONTAMINANT_PROTECTION_DISABLED = 1 << 3;
 
+    /**
+     * USB data status is not known.
+     */
+    public static final int USB_DATA_STATUS_UNKNOWN = 0;
+
+    /**
+     * USB data is enabled.
+     */
+    public static final int USB_DATA_STATUS_ENABLED = 1;
+
+    /**
+     * USB data is disabled as the port is too hot.
+     */
+    public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
+
+    /**
+     * USB data is disabled due to contaminated port.
+     */
+    public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
+
+    /**
+     * USB data is disabled due to docking event.
+     */
+    public static final int USB_DATA_STATUS_DISABLED_DOCK = 4;
+
+    /**
+     * USB data is disabled by
+     * {@link UsbPort#enableUsbData UsbPort.enableUsbData}.
+     */
+    public static final int USB_DATA_STATUS_DISABLED_FORCE = 5;
+
+    /**
+     * USB data is disabled for debug.
+     */
+    public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6;
+
+    /**
+     * Unknown whether a power brick is connected.
+     */
+    public static final int POWER_BRICK_STATUS_UNKNOWN = 0;
+
+    /**
+     * The connected device is a power brick.
+     */
+    public static final int POWER_BRICK_STATUS_CONNECTED = 1;
+
+    /**
+     * The connected device is not power brick.
+     */
+    public static final int POWER_BRICK_STATUS_DISCONNECTED = 2;
+
     @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = {
             CONTAMINANT_DETECTION_NOT_SUPPORTED,
             CONTAMINANT_DETECTION_DISABLED,
@@ -223,18 +276,41 @@
     @interface UsbPortMode{}
 
     /** @hide */
+    @IntDef(prefix = { "USB_DATA_STATUS_" }, value = {
+            USB_DATA_STATUS_UNKNOWN,
+            USB_DATA_STATUS_ENABLED,
+            USB_DATA_STATUS_DISABLED_OVERHEAT,
+            USB_DATA_STATUS_DISABLED_CONTAMINANT,
+            USB_DATA_STATUS_DISABLED_DOCK,
+            USB_DATA_STATUS_DISABLED_FORCE,
+            USB_DATA_STATUS_DISABLED_DEBUG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface UsbDataStatus{}
+
+    /** @hide */
+    @IntDef(prefix = { "POWER_BRICK_STATUS_" }, value = {
+            POWER_BRICK_STATUS_UNKNOWN,
+            POWER_BRICK_STATUS_DISCONNECTED,
+            POWER_BRICK_STATUS_CONNECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PowerBrickStatus{}
+
+    /** @hide */
     public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
             int supportedRoleCombinations, int contaminantProtectionStatus,
-            int contaminantDetectionStatus, boolean usbDataEnabled,
-            boolean powerTransferLimited) {
+            int contaminantDetectionStatus, @UsbDataStatus int[] usbDataStatus,
+            boolean powerTransferLimited, @PowerBrickStatus int powerBrickStatus) {
         mCurrentMode = currentMode;
         mCurrentPowerRole = currentPowerRole;
         mCurrentDataRole = currentDataRole;
         mSupportedRoleCombinations = supportedRoleCombinations;
         mContaminantProtectionStatus = contaminantProtectionStatus;
         mContaminantDetectionStatus = contaminantDetectionStatus;
-        mUsbDataEnabled = usbDataEnabled;
+        mUsbDataStatus = usbDataStatus;
         mPowerTransferLimited = powerTransferLimited;
+        mPowerBrickStatus = powerBrickStatus;
     }
 
     /** @hide */
@@ -247,7 +323,8 @@
         mSupportedRoleCombinations = supportedRoleCombinations;
         mContaminantProtectionStatus = contaminantProtectionStatus;
         mContaminantDetectionStatus = contaminantDetectionStatus;
-        mUsbDataEnabled = true;
+        mUsbDataStatus = new int[]{USB_DATA_STATUS_UNKNOWN};
+        mPowerBrickStatus = POWER_BRICK_STATUS_UNKNOWN;
         mPowerTransferLimited = false;
     }
 
@@ -334,10 +411,14 @@
     /**
      * Returns UsbData status.
      *
-     * @hide
+     * @return Current USB data status of the port: {@link #USB_DATA_STATUS_UNKNOWN}
+     *         or {@link #USB_DATA_STATUS_ENABLED} or {@link #USB_DATA_STATUS_DIASBLED_OVERHEAT}
+     *         or {@link #USB_DATA_STATUS_DISABLED_CONTAMINANT}
+     *         or {@link #USB_DATA_STATUS_DISABLED_DOCK} or {@link #USB_DATA_STATUS_DISABLED_FORCE}
+     *         or {@link #USB_DATA_STATUS_DISABLED_DEBUG}
      */
-    public boolean getUsbDataStatus() {
-        return mUsbDataEnabled;
+    public @UsbDataStatus @Nullable int[] getUsbDataStatus() {
+        return mUsbDataStatus;
     }
 
     /**
@@ -350,6 +431,17 @@
         return mPowerTransferLimited;
     }
 
+    /**
+     * Let's the caller know if a power brick is connected to the USB port.
+     *
+     * @return {@link #POWER_BRICK_STATUS_UNKNOWN}
+     *         or {@link #POWER_BRICK_STATUS_CONNECTED}
+     *         or {@link #POWER_BRICK_STATUS_DISCONNECTED}
+     */
+    public @PowerBrickStatus int getPowerBrickStatus() {
+        return mPowerBrickStatus;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -363,10 +455,12 @@
                         + getContaminantDetectionStatus()
                 + ", contaminantProtectionStatus="
                         + getContaminantProtectionStatus()
-                + ", usbDataEnabled="
-                        + getUsbDataStatus()
+                + ", usbDataStatus="
+                        + UsbPort.usbDataStatusToString(getUsbDataStatus())
                 + ", isPowerTransferLimited="
                         + isPowerTransferLimited()
+                +", powerBrickStatus="
+                        + UsbPort.powerBrickStatusToString(getPowerBrickStatus())
                 + "}";
     }
 
@@ -383,8 +477,10 @@
         dest.writeInt(mSupportedRoleCombinations);
         dest.writeInt(mContaminantProtectionStatus);
         dest.writeInt(mContaminantDetectionStatus);
-        dest.writeBoolean(mUsbDataEnabled);
+        dest.writeInt(mUsbDataStatus.length);
+        dest.writeIntArray(mUsbDataStatus);
         dest.writeBoolean(mPowerTransferLimited);
+        dest.writeInt(mPowerBrickStatus);
     }
 
     public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -397,11 +493,14 @@
             int supportedRoleCombinations = in.readInt();
             int contaminantProtectionStatus = in.readInt();
             int contaminantDetectionStatus = in.readInt();
-            boolean usbDataEnabled = in.readBoolean();
+            int[] usbDataStatus = new int[in.readInt()];
+            in.readIntArray(usbDataStatus);
             boolean powerTransferLimited = in.readBoolean();
+            int powerBrickStatus = in.readInt();
             return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
-                    contaminantDetectionStatus, usbDataEnabled, powerTransferLimited);
+                    contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
+                    powerBrickStatus);
         }
 
         @Override
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 09d5085..5d2d8ea 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -469,6 +469,10 @@
     InputMethodManager mImm;
     private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations();
 
+    @NonNull
+    private final NavigationBarController mNavigationBarController =
+            new NavigationBarController(this);
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     int mTheme = 0;
 
@@ -611,6 +615,7 @@
             info.touchableRegion.set(mTmpInsets.touchableRegion);
             info.setTouchableInsets(mTmpInsets.touchableInsets);
         }
+        mNavigationBarController.updateTouchableInsets(mTmpInsets, info);
 
         if (mInputFrame != null) {
             setImeExclusionRect(mTmpInsets.visibleTopInsets);
@@ -1534,6 +1539,7 @@
         mCandidatesVisibility = getCandidatesHiddenVisibility();
         mCandidatesFrame.setVisibility(mCandidatesVisibility);
         mInputFrame.setVisibility(View.GONE);
+        mNavigationBarController.onViewInitialized();
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
@@ -1543,6 +1549,7 @@
         mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
                 mInsetsComputer);
         doFinishInput();
+        mNavigationBarController.onDestroy();
         mWindow.dismissForDestroyIfNecessary();
         if (mSettingsObserver != null) {
             mSettingsObserver.unregister();
@@ -2451,6 +2458,7 @@
             setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
         }
 
+        mNavigationBarController.onWindowShown();
         // compute visibility
         onWindowShown();
         mWindowVisible = true;
@@ -3656,6 +3664,7 @@
                 + " touchableInsets=" + mTmpInsets.touchableInsets
                 + " touchableRegion=" + mTmpInsets.touchableRegion);
         p.println(" mSettingsObserver=" + mSettingsObserver);
+        p.println(" mNavigationBarController=" + mNavigationBarController.toDebugString());
     }
 
     private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
new file mode 100644
index 0000000..7295b72
--- /dev/null
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.inputmethodservice.navigationbar.NavigationBarFrame;
+import android.inputmethodservice.navigationbar.NavigationBarView;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManagerPolicyConstants;
+import android.widget.FrameLayout;
+
+import java.util.Objects;
+
+/**
+ * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from
+ * {@link InputMethodService}.
+ *
+ * <p>All the package-private methods are no-op when
+ * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p>
+ */
+final class NavigationBarController {
+
+    private interface Callback {
+        default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+                @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+        }
+
+        default void onViewInitialized() {
+        }
+
+        default void onWindowShown() {
+        }
+
+        default void onDestroy() {
+        }
+
+        default String toDebugString() {
+            return "No-op implementation";
+        }
+
+        Callback NOOP = new Callback() {
+        };
+    }
+
+    private final Callback mImpl;
+
+    NavigationBarController(@NonNull InputMethodService inputMethodService) {
+        mImpl = InputMethodService.canImeRenderGesturalNavButtons()
+                ? new Impl(inputMethodService) : Callback.NOOP;
+    }
+
+    void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+            @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+        mImpl.updateTouchableInsets(originalInsets, dest);
+    }
+
+    void onViewInitialized() {
+        mImpl.onViewInitialized();
+    }
+
+    void onWindowShown() {
+        mImpl.onWindowShown();
+    }
+
+    void onDestroy() {
+        mImpl.onDestroy();
+    }
+
+    String toDebugString() {
+        return mImpl.toDebugString();
+    }
+
+    private static final class Impl implements Callback {
+        @NonNull
+        private final InputMethodService mService;
+
+        private boolean mDestroyed = false;
+
+        private boolean mRenderGesturalNavButtons;
+
+        @Nullable
+        private NavigationBarFrame mNavigationBarFrame;
+        @Nullable
+        Insets mLastInsets;
+
+        Impl(@NonNull InputMethodService inputMethodService) {
+            mService = inputMethodService;
+        }
+
+        @Nullable
+        private Insets getSystemInsets() {
+            if (mService.mWindow == null) {
+                return null;
+            }
+            final View decorView = mService.mWindow.getWindow().getDecorView();
+            if (decorView == null) {
+                return null;
+            }
+            final WindowInsets windowInsets = decorView.getRootWindowInsets();
+            if (windowInsets == null) {
+                return null;
+            }
+            final Insets stableBarInsets =
+                    windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
+            return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars()
+                    | WindowInsets.Type.displayCutout()), stableBarInsets);
+        }
+
+        private void installNavigationBarFrameIfNecessary() {
+            if (!mRenderGesturalNavButtons) {
+                return;
+            }
+            final View rawDecorView = mService.mWindow.getWindow().getDecorView();
+            if (!(rawDecorView instanceof ViewGroup)) {
+                return;
+            }
+            final ViewGroup decorView = (ViewGroup) rawDecorView;
+            mNavigationBarFrame = decorView.findViewByPredicate(
+                    NavigationBarFrame.class::isInstance);
+            final Insets systemInsets = getSystemInsets();
+            if (mNavigationBarFrame == null) {
+                mNavigationBarFrame = new NavigationBarFrame(mService);
+                LayoutInflater.from(mService).inflate(
+                        com.android.internal.R.layout.input_method_navigation_bar,
+                        mNavigationBarFrame);
+                if (systemInsets != null) {
+                    decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            systemInsets.bottom, Gravity.BOTTOM));
+                    mLastInsets = systemInsets;
+                } else {
+                    decorView.addView(mNavigationBarFrame);
+                }
+                final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
+                        NavigationBarView.class::isInstance);
+                if (navigationBarView != null) {
+                    // TODO(b/213337792): Support InputMethodService#setBackDisposition().
+                    // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
+                    final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
+                            | StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+                    navigationBarView.setNavigationIconHints(hints);
+                }
+            } else {
+                mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM));
+                mLastInsets = systemInsets;
+            }
+
+            mNavigationBarFrame.setBackground(null);
+        }
+
+        @Override
+        public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
+                @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
+            if (!mRenderGesturalNavButtons || mNavigationBarFrame == null
+                    || mService.isExtractViewShown()) {
+                return;
+            }
+
+            final Insets systemInsets = getSystemInsets();
+            if (systemInsets != null) {
+                final Window window = mService.mWindow.getWindow();
+                final View decor = window.getDecorView();
+                Region touchableRegion = null;
+                final View inputFrame = mService.mInputFrame;
+                switch (originalInsets.touchableInsets) {
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    inputFrame.getTop(), inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    originalInsets.contentTopInsets, inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+                        if (inputFrame.getVisibility() == View.VISIBLE) {
+                            touchableRegion = new Region(inputFrame.getLeft(),
+                                    originalInsets.visibleTopInsets, inputFrame.getRight(),
+                                    inputFrame.getBottom());
+                        }
+                        break;
+                    case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
+                        touchableRegion = new Region();
+                        touchableRegion.set(originalInsets.touchableRegion);
+                        break;
+                }
+                final Rect navBarRect = new Rect(decor.getLeft(),
+                        decor.getBottom() - systemInsets.bottom,
+                        decor.getRight(), decor.getBottom());
+                if (touchableRegion == null) {
+                    touchableRegion = new Region(navBarRect);
+                } else {
+                    touchableRegion.union(navBarRect);
+                }
+
+                dest.touchableRegion.set(touchableRegion);
+                dest.setTouchableInsets(
+                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+                // TODO(b/205803355): See if we can use View#OnLayoutChangeListener().
+                // TODO(b/205803355): See if we can replace DecorView#mNavigationColorViewState.view
+                boolean zOrderChanged = false;
+                if (decor instanceof ViewGroup) {
+                    ViewGroup decorGroup = (ViewGroup) decor;
+                    final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+                    zOrderChanged = navbarBackgroundView != null
+                            && decorGroup.indexOfChild(navbarBackgroundView)
+                            > decorGroup.indexOfChild(mNavigationBarFrame);
+                }
+                final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
+                if (zOrderChanged || insetChanged) {
+                    final NavigationBarFrame that = mNavigationBarFrame;
+                    that.post(() -> {
+                        if (!that.isAttachedToWindow()) {
+                            return;
+                        }
+                        final Insets currentSystemInsets = getSystemInsets();
+                        if (!Objects.equals(currentSystemInsets, mLastInsets)) {
+                            that.setLayoutParams(
+                                    new FrameLayout.LayoutParams(
+                                            ViewGroup.LayoutParams.MATCH_PARENT,
+                                            currentSystemInsets.bottom, Gravity.BOTTOM));
+                            mLastInsets = currentSystemInsets;
+                        }
+                        if (decor instanceof ViewGroup) {
+                            ViewGroup decorGroup = (ViewGroup) decor;
+                            final View navbarBackgroundView =
+                                    window.getNavigationBarBackgroundView();
+                            if (navbarBackgroundView != null
+                                    && decorGroup.indexOfChild(navbarBackgroundView)
+                                    > decorGroup.indexOfChild(that)) {
+                                decorGroup.bringChildToFront(that);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        private boolean isGesturalNavigationEnabled() {
+            final Resources resources = mService.getResources();
+            if (resources == null) {
+                return false;
+            }
+            return resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode)
+                    == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+        }
+
+        @Override
+        public void onViewInitialized() {
+            if (mDestroyed) {
+                return;
+            }
+            mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+            installNavigationBarFrameIfNecessary();
+        }
+
+        @Override
+        public void onDestroy() {
+            mDestroyed = true;
+        }
+
+        @Override
+        public void onWindowShown() {
+            if (mDestroyed || !mRenderGesturalNavButtons || mNavigationBarFrame == null) {
+                return;
+            }
+            final Insets systemInsets = getSystemInsets();
+            if (systemInsets != null) {
+                if (!Objects.equals(systemInsets, mLastInsets)) {
+                    mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            systemInsets.bottom, Gravity.BOTTOM));
+                    mLastInsets = systemInsets;
+                }
+                final Window window = mService.mWindow.getWindow();
+                View rawDecorView = window.getDecorView();
+                if (rawDecorView instanceof ViewGroup) {
+                    final ViewGroup decor = (ViewGroup) rawDecorView;
+                    final View navbarBackgroundView = window.getNavigationBarBackgroundView();
+                    if (navbarBackgroundView != null
+                            && decor.indexOfChild(navbarBackgroundView)
+                            > decor.indexOfChild(mNavigationBarFrame)) {
+                        decor.bringChildToFront(mNavigationBarFrame);
+                    }
+                }
+                mNavigationBarFrame.setVisibility(View.VISIBLE);
+            }
+        }
+
+        @Override
+        public String toDebugString() {
+            return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}";
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
new file mode 100644
index 0000000..3f26fa4
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Dispatches common view calls to multiple views.  This is used to handle
+ * multiples of the same nav bar icon appearing.
+ */
+final class ButtonDispatcher {
+    private static final int FADE_DURATION_IN = 150;
+    private static final int FADE_DURATION_OUT = 250;
+    public static final Interpolator LINEAR = new LinearInterpolator();
+
+    private final ArrayList<View> mViews = new ArrayList<>();
+
+    private final int mId;
+
+    private View.OnClickListener mClickListener;
+    private View.OnTouchListener mTouchListener;
+    private View.OnLongClickListener mLongClickListener;
+    private View.OnHoverListener mOnHoverListener;
+    private Boolean mLongClickable;
+    private float mAlpha = 1.0f;
+    private Float mDarkIntensity;
+    private int mVisibility = View.VISIBLE;
+    private Boolean mDelayTouchFeedback;
+    private KeyButtonDrawable mImageDrawable;
+    private View mCurrentView;
+    private ValueAnimator mFadeAnimator;
+    private AccessibilityDelegate mAccessibilityDelegate;
+
+    private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation ->
+            setAlpha(
+                    (float) animation.getAnimatedValue(),
+                    false /* animate */,
+                    false /* cancelAnimator */);
+
+    private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mFadeAnimator = null;
+            setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE);
+        }
+    };
+
+    public ButtonDispatcher(int id) {
+        mId = id;
+    }
+
+    public void clear() {
+        mViews.clear();
+    }
+
+    public void addView(View view) {
+        mViews.add(view);
+        view.setOnClickListener(mClickListener);
+        view.setOnTouchListener(mTouchListener);
+        view.setOnLongClickListener(mLongClickListener);
+        view.setOnHoverListener(mOnHoverListener);
+        if (mLongClickable != null) {
+            view.setLongClickable(mLongClickable);
+        }
+        view.setAlpha(mAlpha);
+        view.setVisibility(mVisibility);
+        if (mAccessibilityDelegate != null) {
+            view.setAccessibilityDelegate(mAccessibilityDelegate);
+        }
+        if (view instanceof ButtonInterface) {
+            final ButtonInterface button = (ButtonInterface) view;
+            if (mDarkIntensity != null) {
+                button.setDarkIntensity(mDarkIntensity);
+            }
+            if (mImageDrawable != null) {
+                button.setImageDrawable(mImageDrawable);
+            }
+            if (mDelayTouchFeedback != null) {
+                button.setDelayTouchFeedback(mDelayTouchFeedback);
+            }
+        }
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public int getVisibility() {
+        return mVisibility;
+    }
+
+    public boolean isVisible() {
+        return getVisibility() == View.VISIBLE;
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    public KeyButtonDrawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
+    public void setImageDrawable(KeyButtonDrawable drawable) {
+        mImageDrawable = drawable;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable);
+            }
+        }
+        if (mImageDrawable != null) {
+            mImageDrawable.setCallback(mCurrentView);
+        }
+    }
+
+    public void setVisibility(int visibility) {
+        if (mVisibility == visibility) return;
+        if (mFadeAnimator != null) {
+            mFadeAnimator.cancel();
+        }
+
+        mVisibility = visibility;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setVisibility(mVisibility);
+        }
+    }
+
+    public void setAlpha(float alpha) {
+        setAlpha(alpha, false /* animate */);
+    }
+
+    public void setAlpha(float alpha, boolean animate) {
+        setAlpha(alpha, animate, true /* cancelAnimator */);
+    }
+
+    public void setAlpha(float alpha, boolean animate, long duration) {
+        setAlpha(alpha, animate, duration, true /* cancelAnimator */);
+    }
+
+    public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) {
+        setAlpha(
+                alpha,
+                animate,
+                (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT,
+                cancelAnimator);
+    }
+
+    public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) {
+        if (mFadeAnimator != null && (cancelAnimator || animate)) {
+            mFadeAnimator.cancel();
+        }
+        if (animate) {
+            setVisibility(View.VISIBLE);
+            mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha);
+            mFadeAnimator.setDuration(duration);
+            mFadeAnimator.setInterpolator(LINEAR);
+            mFadeAnimator.addListener(mFadeListener);
+            mFadeAnimator.addUpdateListener(mAlphaListener);
+            mFadeAnimator.start();
+        } else {
+            // Discretize the alpha updates to prevent too frequent updates when there is a long
+            // alpha animation
+            int prevAlpha = (int) (getAlpha() * 255);
+            int nextAlpha = (int) (alpha * 255);
+            if (prevAlpha != nextAlpha) {
+                mAlpha = nextAlpha / 255f;
+                final int N = mViews.size();
+                for (int i = 0; i < N; i++) {
+                    mViews.get(i).setAlpha(mAlpha);
+                }
+            }
+        }
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        mDarkIntensity = darkIntensity;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity);
+            }
+        }
+    }
+
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            if (mViews.get(i) instanceof ButtonInterface) {
+                ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay);
+            }
+        }
+    }
+
+    public void setOnClickListener(View.OnClickListener clickListener) {
+        mClickListener = clickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnClickListener(mClickListener);
+        }
+    }
+
+    public void setOnTouchListener(View.OnTouchListener touchListener) {
+        mTouchListener = touchListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnTouchListener(mTouchListener);
+        }
+    }
+
+    public void setLongClickable(boolean isLongClickable) {
+        mLongClickable = isLongClickable;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setLongClickable(mLongClickable);
+        }
+    }
+
+    public void setOnLongClickListener(View.OnLongClickListener longClickListener) {
+        mLongClickListener = longClickListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnLongClickListener(mLongClickListener);
+        }
+    }
+
+    public void setOnHoverListener(View.OnHoverListener hoverListener) {
+        mOnHoverListener = hoverListener;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setOnHoverListener(mOnHoverListener);
+        }
+    }
+
+    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+        mAccessibilityDelegate = delegate;
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            mViews.get(i).setAccessibilityDelegate(delegate);
+        }
+    }
+
+    public void setTranslation(int x, int y, int z) {
+        final int N = mViews.size();
+        for (int i = 0; i < N; i++) {
+            final View view = mViews.get(i);
+            view.setTranslationX(x);
+            view.setTranslationY(y);
+            view.setTranslationZ(z);
+        }
+    }
+
+    public ArrayList<View> getViews() {
+        return mViews;
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    public void setCurrentView(View currentView) {
+        mCurrentView = currentView.findViewById(mId);
+        if (mImageDrawable != null) {
+            mImageDrawable.setCallback(mCurrentView);
+        }
+        if (mCurrentView != null) {
+            mCurrentView.setTranslationX(0);
+            mCurrentView.setTranslationY(0);
+            mCurrentView.setTranslationZ(0);
+        }
+    }
+
+    /**
+     * Executes when button is detached from window.
+     */
+    public void onDestroy() {
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
new file mode 100644
index 0000000..1c9c86d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+
+interface ButtonInterface {
+
+    void setImageDrawable(@Nullable Drawable drawable);
+
+    void setDarkIntensity(float intensity);
+
+    void setDelayTouchFeedback(boolean shouldDelay);
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
new file mode 100644
index 0000000..cd85736
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_DECAY;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_HOLD;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE_MAX;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ObjectAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+/**
+ * The "dead zone" consumes unintentional taps along the top edge of the navigation bar.
+ * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and
+ * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI
+ * outside the navigation bar (since this is when accidental taps are more likely), then contracts
+ * back over time (since a later tap might be intended for the top of the bar).
+ */
+final class DeadZone {
+    public static final String TAG = "DeadZone";
+
+    public static final boolean DEBUG = false;
+    public static final int HORIZONTAL = 0;  // Consume taps along the top edge.
+    public static final int VERTICAL = 1;  // Consume taps along the left edge.
+
+    private static final boolean CHATTY = true; // print to logcat when we eat a click
+    private final NavigationBarView mNavigationBarView;
+
+    private boolean mShouldFlash;
+    private float mFlashFrac = 0f;
+
+    private int mSizeMax;
+    private int mSizeMin;
+    // Upon activity elsewhere in the UI, the dead zone will hold steady for
+    // mHold ms, then move back over the course of mDecay ms
+    private int mHold, mDecay;
+    private boolean mVertical;
+    private long mLastPokeTime;
+    private int mDisplayRotation;
+
+    private final Runnable mDebugFlash = new Runnable() {
+        @Override
+        public void run() {
+            ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start();
+        }
+    };
+
+    public DeadZone(NavigationBarView view) {
+        mNavigationBarView = view;
+        onConfigurationChanged(Surface.ROTATION_0);
+    }
+
+    static float lerp(float a, float b, float f) {
+        return (b - a) * f + a;
+    }
+
+    private float getSize(long now) {
+        if (mSizeMax == 0)
+            return 0;
+        long dt = (now - mLastPokeTime);
+        if (dt > mHold + mDecay)
+            return mSizeMin;
+        if (dt < mHold)
+            return mSizeMax;
+        return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay);
+    }
+
+    public void setFlashOnTouchCapture(boolean dbg) {
+        mShouldFlash = dbg;
+        mFlashFrac = 0f;
+        mNavigationBarView.postInvalidate();
+    }
+
+    public void onConfigurationChanged(int rotation) {
+        mDisplayRotation = rotation;
+
+        final Resources res = mNavigationBarView.getResources();
+        mHold = NAVIGATION_BAR_DEADZONE_HOLD;
+        mDecay = NAVIGATION_BAR_DEADZONE_DECAY;
+
+        mSizeMin = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE, res);
+        mSizeMax = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE_MAX, res);
+        mVertical = (res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
+
+        if (DEBUG) {
+            Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
+                    + (mVertical ? " vertical" : " horizontal"));
+        }
+        setFlashOnTouchCapture(false);   // hard-coded from "bool/config_dead_zone_flash"
+    }
+
+    // I made you a touch event...
+    public boolean onTouchEvent(MotionEvent event) {
+        if (DEBUG) {
+            Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
+        }
+
+        // Don't consume events for high precision pointing devices. For this purpose a stylus is
+        // considered low precision (like a finger), so its events may be consumed.
+        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+            return false;
+        }
+
+        final int action = event.getAction();
+        if (action == MotionEvent.ACTION_OUTSIDE) {
+            poke(event);
+            return true;
+        } else if (action == MotionEvent.ACTION_DOWN) {
+            if (DEBUG) {
+                Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
+            }
+            //TODO(b/205803355): call mNavBarController.touchAutoDim(mDisplayId); here
+            int size = (int) getSize(event.getEventTime());
+            // In the vertical orientation consume taps along the left edge.
+            // In horizontal orientation consume taps along the top edge.
+            final boolean consumeEvent;
+            if (mVertical) {
+                if (mDisplayRotation == Surface.ROTATION_270) {
+                    consumeEvent = event.getX() > mNavigationBarView.getWidth() - size;
+                } else {
+                    consumeEvent = event.getX() < size;
+                }
+            } else {
+                consumeEvent = event.getY() < size;
+            }
+            if (consumeEvent) {
+                if (CHATTY) {
+                    Log.v(TAG, "consuming errant click: (" + event.getX() + ","
+                            + event.getY() + ")");
+                }
+                if (mShouldFlash) {
+                    mNavigationBarView.post(mDebugFlash);
+                    mNavigationBarView.postInvalidate();
+                }
+                return true; // ...but I eated it
+            }
+        }
+        return false;
+    }
+
+    private void poke(MotionEvent event) {
+        mLastPokeTime = event.getEventTime();
+        if (DEBUG)
+            Log.v(TAG, "poked! size=" + getSize(mLastPokeTime));
+        if (mShouldFlash) mNavigationBarView.postInvalidate();
+    }
+
+    public void setFlash(float f) {
+        mFlashFrac = f;
+        mNavigationBarView.postInvalidate();
+    }
+
+    public float getFlash() {
+        return mFlashFrac;
+    }
+
+    public void onDraw(Canvas can) {
+        if (!mShouldFlash || mFlashFrac <= 0f) {
+            return;
+        }
+
+        final int size = (int) getSize(SystemClock.uptimeMillis());
+        if (mVertical) {
+            if (mDisplayRotation == Surface.ROTATION_270) {
+                can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight());
+            } else {
+                can.clipRect(0, 0, size, can.getHeight());
+            }
+        } else {
+            can.clipRect(0, 0, can.getWidth(), size);
+        }
+
+        final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
+        can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
+
+        if (DEBUG && size > mSizeMin) {
+            // Very aggressive redrawing here, for debugging only
+            mNavigationBarView.postInvalidateDelayed(100);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
new file mode 100644
index 0000000..25a443d
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_COLOR;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_X;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_Y;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_RADIUS;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+
+import android.animation.ArgbEvaluator;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.view.View;
+
+
+/**
+ * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows
+ * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support
+ * for shadows nor rotations.
+ */
+final class KeyButtonDrawable extends Drawable {
+
+    public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE =
+        new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") {
+            @Override
+            public void setValue(KeyButtonDrawable drawable, float degree) {
+                drawable.setRotation(degree);
+            }
+
+            @Override
+            public Float get(KeyButtonDrawable drawable) {
+                return drawable.getRotation();
+            }
+        };
+
+    public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y =
+        new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") {
+            @Override
+            public void setValue(KeyButtonDrawable drawable, float y) {
+                drawable.setTranslationY(y);
+            }
+
+            @Override
+            public Float get(KeyButtonDrawable drawable) {
+                return drawable.getTranslationY();
+            }
+        };
+
+    private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final ShadowDrawableState mState;
+    private AnimatedVectorDrawable mAnimatedDrawable;
+    private final Callback mAnimatedDrawableCallback = new Callback() {
+        @Override
+        public void invalidateDrawable(@NonNull Drawable who) {
+            invalidateSelf();
+        }
+
+        @Override
+        public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+            scheduleSelf(what, when);
+        }
+
+        @Override
+        public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+            unscheduleSelf(what);
+        }
+    };
+
+    public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor,
+            boolean horizontalFlip, Color ovalBackgroundColor) {
+        this(d, new ShadowDrawableState(lightColor, darkColor,
+                d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor));
+    }
+
+    private KeyButtonDrawable(Drawable d, ShadowDrawableState state) {
+        mState = state;
+        if (d != null) {
+            mState.mBaseHeight = d.getIntrinsicHeight();
+            mState.mBaseWidth = d.getIntrinsicWidth();
+            mState.mChangingConfigurations = d.getChangingConfigurations();
+            mState.mChildState = d.getConstantState();
+        }
+        if (canAnimate()) {
+            mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate();
+            mAnimatedDrawable.setCallback(mAnimatedDrawableCallback);
+            setDrawableBounds(mAnimatedDrawable);
+        }
+    }
+
+    public void setDarkIntensity(float intensity) {
+        mState.mDarkIntensity = intensity;
+        final int color = (int) ArgbEvaluator.getInstance()
+                .evaluate(intensity, mState.mLightColor, mState.mDarkColor);
+        updateShadowAlpha();
+        setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP));
+    }
+
+    public void setRotation(float degrees) {
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support rotation
+            return;
+        }
+        if (mState.mRotateDegrees != degrees) {
+            mState.mRotateDegrees = degrees;
+            invalidateSelf();
+        }
+    }
+
+    public void setTranslationX(float x) {
+        setTranslation(x, mState.mTranslationY);
+    }
+
+    public void setTranslationY(float y) {
+        setTranslation(mState.mTranslationX, y);
+    }
+
+    public void setTranslation(float x, float y) {
+        if (mState.mTranslationX != x || mState.mTranslationY != y) {
+            mState.mTranslationX = x;
+            mState.mTranslationY = y;
+            invalidateSelf();
+        }
+    }
+
+    public void setShadowProperties(int x, int y, int size, int color) {
+        if (canAnimate()) {
+            // AnimatedVectorDrawables will not support shadows
+            return;
+        }
+        if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y
+                || mState.mShadowSize != size || mState.mShadowColor != color) {
+            mState.mShadowOffsetX = x;
+            mState.mShadowOffsetY = y;
+            mState.mShadowSize = size;
+            mState.mShadowColor = color;
+            mShadowPaint.setColorFilter(
+                    new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP));
+            updateShadowAlpha();
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        super.jumpToCurrentState();
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.jumpToCurrentState();
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mState.mAlpha = alpha;
+        mIconPaint.setAlpha(alpha);
+        updateShadowAlpha();
+        invalidateSelf();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mIconPaint.setColorFilter(colorFilter);
+        if (mAnimatedDrawable != null) {
+            if (hasOvalBg()) {
+                mAnimatedDrawable.setColorFilter(
+                        new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN));
+            } else {
+                mAnimatedDrawable.setColorFilter(colorFilter);
+            }
+        }
+        invalidateSelf();
+    }
+
+    public float getDarkIntensity() {
+        return mState.mDarkIntensity;
+    }
+
+    public float getRotation() {
+        return mState.mRotateDegrees;
+    }
+
+    public float getTranslationX() {
+        return mState.mTranslationX;
+    }
+
+    public float getTranslationY() {
+        return mState.mTranslationY;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mState;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2;
+    }
+
+    public boolean canAnimate() {
+        return mState.mSupportsAnimation;
+    }
+
+    public void startAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.start();
+        }
+    }
+
+    public void resetAnimation() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.reset();
+        }
+    }
+
+    public void clearAnimationCallbacks() {
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.clearAnimationCallbacks();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        Rect bounds = getBounds();
+        if (bounds.isEmpty()) {
+            return;
+        }
+
+        if (mAnimatedDrawable != null) {
+            mAnimatedDrawable.draw(canvas);
+        } else {
+            // If no cache or previous cached bitmap is hardware/software acceleration does not
+            // match the current canvas on draw then regenerate
+            boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated();
+            if (hwBitmapChanged) {
+                mState.mIsHardwareBitmap = canvas.isHardwareAccelerated();
+            }
+            if (mState.mLastDrawnIcon == null || hwBitmapChanged) {
+                regenerateBitmapIconCache();
+            }
+            canvas.save();
+            canvas.translate(mState.mTranslationX, mState.mTranslationY);
+            canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2);
+
+            if (mState.mShadowSize > 0) {
+                if (mState.mLastDrawnShadow == null || hwBitmapChanged) {
+                    regenerateBitmapShadowCache();
+                }
+
+                // Translate (with rotation offset) before drawing the shadow
+                final float radians = (float) (mState.mRotateDegrees * Math.PI / 180);
+                final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY
+                        + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX;
+                final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY
+                        - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY;
+                canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY,
+                        mShadowPaint);
+            }
+            canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint);
+            canvas.restore();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return mState.canApplyTheme();
+    }
+
+    @ColorInt int getDrawableBackgroundColor() {
+        return mState.mOvalBackgroundColor.toArgb();
+    }
+
+    boolean hasOvalBg() {
+        return mState.mOvalBackgroundColor != null;
+    }
+
+    private void regenerateBitmapIconCache() {
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
+        d.draw(canvas);
+        canvas.restore();
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnIcon = bitmap;
+    }
+
+    private void regenerateBitmapShadowCache() {
+        if (mState.mShadowSize == 0) {
+            // No shadow
+            mState.mLastDrawnIcon = null;
+            return;
+        }
+
+        final int width = getIntrinsicWidth();
+        final int height = getIntrinsicHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
+        final Drawable d = mState.mChildState.newDrawable().mutate();
+        setDrawableBounds(d);
+        canvas.save();
+        if (mState.mHorizontalFlip) {
+            canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f);
+        }
+        d.draw(canvas);
+        canvas.restore();
+
+        // Draws the shadow from original drawable
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL));
+        int[] offset = new int[2];
+        final Bitmap shadow = bitmap.extractAlpha(paint, offset);
+        paint.setMaskFilter(null);
+        bitmap.eraseColor(Color.TRANSPARENT);
+        canvas.drawBitmap(shadow, offset[0], offset[1], paint);
+
+        if (mState.mIsHardwareBitmap) {
+            bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
+        }
+        mState.mLastDrawnShadow = bitmap;
+    }
+
+    /**
+     * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since
+     * dark color and shadow should not be visible at the same time.
+     */
+    private void updateShadowAlpha() {
+        // Update the color from the original color's alpha as the max
+        int alpha = Color.alpha(mState.mShadowColor);
+        mShadowPaint.setAlpha(
+                Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity)));
+    }
+
+    /**
+     * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset
+     * @param d the drawable to set the bounds
+     */
+    private void setDrawableBounds(Drawable d) {
+        final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX);
+        final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY);
+        d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX,
+                getIntrinsicHeight() - offsetY);
+    }
+
+    private static class ShadowDrawableState extends ConstantState {
+        int mChangingConfigurations;
+        int mBaseWidth;
+        int mBaseHeight;
+        float mRotateDegrees;
+        float mTranslationX;
+        float mTranslationY;
+        int mShadowOffsetX;
+        int mShadowOffsetY;
+        int mShadowSize;
+        int mShadowColor;
+        float mDarkIntensity;
+        int mAlpha;
+        boolean mHorizontalFlip;
+
+        boolean mIsHardwareBitmap;
+        Bitmap mLastDrawnIcon;
+        Bitmap mLastDrawnShadow;
+        ConstantState mChildState;
+
+        final int mLightColor;
+        final int mDarkColor;
+        final boolean mSupportsAnimation;
+        final Color mOvalBackgroundColor;
+
+        public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor,
+                boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) {
+            mLightColor = lightColor;
+            mDarkColor = darkColor;
+            mSupportsAnimation = animated;
+            mAlpha = 255;
+            mHorizontalFlip = horizontalFlip;
+            mOvalBackgroundColor = ovalBackgroundColor;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new KeyButtonDrawable(null, this);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            return true;
+        }
+    }
+
+    /**
+     * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see
+     * {@link #create(Context, int, boolean, boolean)}.
+     */
+    public static KeyButtonDrawable create(Context context, @ColorInt int lightColor,
+            @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow,
+            Color ovalBackgroundColor) {
+        final Resources res = context.getResources();
+        boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        Drawable d = context.getDrawable(iconResId);
+        final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor,
+                isRtl && d.isAutoMirrored(), ovalBackgroundColor);
+        if (hasShadow) {
+            int offsetX = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_X, res);
+            int offsetY = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_Y, res);
+            int radius = dpToPx(NAV_KEY_BUTTON_SHADOW_RADIUS, res);
+            int color = NAV_KEY_BUTTON_SHADOW_COLOR;
+            drawable.setShadowProperties(offsetX, offsetY, radius, color);
+        }
+        return drawable;
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
new file mode 100644
index 0000000..38a63b6
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.DimenRes;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RecordingCanvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Trace;
+import android.view.RenderNodeAnimator;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+final class KeyButtonRipple extends Drawable {
+
+    private static final float GLOW_MAX_SCALE_FACTOR = 1.35f;
+    private static final float GLOW_MAX_ALPHA = 0.2f;
+    private static final float GLOW_MAX_ALPHA_DARK = 0.1f;
+    private static final int ANIMATION_DURATION_SCALE = 350;
+    private static final int ANIMATION_DURATION_FADE = 450;
+    private static final Interpolator ALPHA_OUT_INTERPOLATOR =
+            new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+    @DimenRes
+    private final int mMaxWidthResource;
+
+    private Paint mRipplePaint;
+    private CanvasProperty<Float> mLeftProp;
+    private CanvasProperty<Float> mTopProp;
+    private CanvasProperty<Float> mRightProp;
+    private CanvasProperty<Float> mBottomProp;
+    private CanvasProperty<Float> mRxProp;
+    private CanvasProperty<Float> mRyProp;
+    private CanvasProperty<Paint> mPaintProp;
+    private float mGlowAlpha = 0f;
+    private float mGlowScale = 1f;
+    private boolean mPressed;
+    private boolean mVisible;
+    private boolean mDrawingHardwareGlow;
+    private int mMaxWidth;
+    private boolean mLastDark;
+    private boolean mDark;
+    private boolean mDelayTouchFeedback;
+
+    private final Interpolator mInterpolator = new LogInterpolator();
+    private boolean mSupportHardware;
+    private final View mTargetView;
+    private final Handler mHandler = new Handler();
+
+    private final HashSet<Animator> mRunningAnimations = new HashSet<>();
+    private final ArrayList<Animator> mTmpArray = new ArrayList<>();
+
+    private final TraceAnimatorListener mExitHwTraceAnimator =
+            new TraceAnimatorListener("exitHardware");
+    private final TraceAnimatorListener mEnterHwTraceAnimator =
+            new TraceAnimatorListener("enterHardware");
+
+    public enum Type {
+        OVAL,
+        ROUNDED_RECT
+    }
+
+    private Type mType = Type.ROUNDED_RECT;
+
+    public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) {
+        mMaxWidthResource = maxWidthResource;
+        mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource);
+        mTargetView = targetView;
+    }
+
+    public void updateResources() {
+        mMaxWidth = mTargetView.getContext().getResources()
+                .getDimensionPixelSize(mMaxWidthResource);
+        invalidateSelf();
+    }
+
+    public void setDarkIntensity(float darkIntensity) {
+        mDark = darkIntensity >= 0.5f;
+    }
+
+    public void setDelayTouchFeedback(boolean delay) {
+        mDelayTouchFeedback = delay;
+    }
+
+    public void setType(Type type) {
+        mType = type;
+    }
+
+    private Paint getRipplePaint() {
+        if (mRipplePaint == null) {
+            mRipplePaint = new Paint();
+            mRipplePaint.setAntiAlias(true);
+            mRipplePaint.setColor(mLastDark ? 0xff000000 : 0xffffffff);
+        }
+        return mRipplePaint;
+    }
+
+    private void drawSoftware(Canvas canvas) {
+        if (mGlowAlpha > 0f) {
+            final Paint p = getRipplePaint();
+            p.setAlpha((int)(mGlowAlpha * 255f));
+
+            final float w = getBounds().width();
+            final float h = getBounds().height();
+            final boolean horizontal = w > h;
+            final float diameter = getRippleSize() * mGlowScale;
+            final float radius = diameter * .5f;
+            final float cx = w * .5f;
+            final float cy = h * .5f;
+            final float rx = horizontal ? radius : cx;
+            final float ry = horizontal ? cy : radius;
+            final float corner = horizontal ? cy : cx;
+
+            if (mType == Type.ROUNDED_RECT) {
+                canvas.drawRoundRect(cx - rx, cy - ry, cx + rx, cy + ry, corner, corner, p);
+            } else {
+                canvas.save();
+                canvas.translate(cx, cy);
+                float r = Math.min(rx, ry);
+                canvas.drawOval(-r, -r, r, r, p);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mSupportHardware = canvas.isHardwareAccelerated();
+        if (mSupportHardware) {
+            drawHardware((RecordingCanvas) canvas);
+        } else {
+            drawSoftware(canvas);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        // Not supported.
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        // Not supported.
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    private boolean isHorizontal() {
+        return getBounds().width() > getBounds().height();
+    }
+
+    private void drawHardware(RecordingCanvas c) {
+        if (mDrawingHardwareGlow) {
+            if (mType == Type.ROUNDED_RECT) {
+                c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp,
+                        mPaintProp);
+            } else {
+                CanvasProperty<Float> cx = CanvasProperty.createFloat(getBounds().width() / 2);
+                CanvasProperty<Float> cy = CanvasProperty.createFloat(getBounds().height() / 2);
+                int d = Math.min(getBounds().width(), getBounds().height());
+                CanvasProperty<Float> r = CanvasProperty.createFloat(1.0f * d / 2);
+                c.drawCircle(cx, cy, r, mPaintProp);
+            }
+        }
+    }
+
+    /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public float getGlowAlpha() {
+        return mGlowAlpha;
+    }
+
+    /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public void setGlowAlpha(float x) {
+        mGlowAlpha = x;
+        invalidateSelf();
+    }
+
+    /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public float getGlowScale() {
+        return mGlowScale;
+    }
+
+    /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */
+    public void setGlowScale(float x) {
+        mGlowScale = x;
+        invalidateSelf();
+    }
+
+    private float getMaxGlowAlpha() {
+        return mLastDark ? GLOW_MAX_ALPHA_DARK : GLOW_MAX_ALPHA;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean pressed = false;
+        for (int i = 0; i < state.length; i++) {
+            if (state[i] == android.R.attr.state_pressed) {
+                pressed = true;
+                break;
+            }
+        }
+        if (pressed != mPressed) {
+            setPressed(pressed);
+            mPressed = pressed;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+        if (changed) {
+            // End any existing animations when the visibility changes
+            jumpToCurrentState();
+        }
+        return changed;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        endAnimations("jumpToCurrentState", false /* cancel */);
+    }
+
+    @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    @Override
+    public boolean hasFocusStateSpecified() {
+        return true;
+    }
+
+    public void setPressed(boolean pressed) {
+        if (mDark != mLastDark && pressed) {
+            mRipplePaint = null;
+            mLastDark = mDark;
+        }
+        if (mSupportHardware) {
+            setPressedHardware(pressed);
+        } else {
+            setPressedSoftware(pressed);
+        }
+    }
+
+    /**
+     * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch
+     * is enabled.
+     */
+    public void abortDelayedRipple() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void endAnimations(String reason, boolean cancel) {
+        Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+        Trace.endSection();
+        mVisible = false;
+        mTmpArray.addAll(mRunningAnimations);
+        int size = mTmpArray.size();
+        for (int i = 0; i < size; i++) {
+            Animator a = mTmpArray.get(i);
+            if (cancel) {
+                a.cancel();
+            } else {
+                a.end();
+            }
+        }
+        mTmpArray.clear();
+        mRunningAnimations.clear();
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    private void setPressedSoftware(boolean pressed) {
+        if (pressed) {
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterSoftware();
+                }
+            } else {
+                enterSoftware();
+            }
+        } else {
+            exitSoftware();
+        }
+    }
+
+    private void enterSoftware() {
+        endAnimations("enterSoftware", true /* cancel */);
+        mVisible = true;
+        mGlowAlpha = getMaxGlowAlpha();
+        ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale",
+                0f, GLOW_MAX_SCALE_FACTOR);
+        scaleAnimator.setInterpolator(mInterpolator);
+        scaleAnimator.setDuration(ANIMATION_DURATION_SCALE);
+        scaleAnimator.addListener(mAnimatorListener);
+        scaleAnimator.start();
+        mRunningAnimations.add(scaleAnimator);
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitSoftware();
+        }
+    }
+
+    private void exitSoftware() {
+        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f);
+        alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+        alphaAnimator.setDuration(ANIMATION_DURATION_FADE);
+        alphaAnimator.addListener(mAnimatorListener);
+        alphaAnimator.start();
+        mRunningAnimations.add(alphaAnimator);
+    }
+
+    private void setPressedHardware(boolean pressed) {
+        if (pressed) {
+            if (mDelayTouchFeedback) {
+                if (mRunningAnimations.isEmpty()) {
+                    mHandler.removeCallbacksAndMessages(null);
+                    mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout());
+                } else if (mVisible) {
+                    enterHardware();
+                }
+            } else {
+                enterHardware();
+            }
+        } else {
+            exitHardware();
+        }
+    }
+
+    /**
+     * Sets the left/top property for the round rect to {@code prop} depending on whether we are
+     * horizontal or vertical mode.
+     */
+    private void setExtendStart(CanvasProperty<Float> prop) {
+        if (isHorizontal()) {
+            mLeftProp = prop;
+        } else {
+            mTopProp = prop;
+        }
+    }
+
+    private CanvasProperty<Float> getExtendStart() {
+        return isHorizontal() ? mLeftProp : mTopProp;
+    }
+
+    /**
+     * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are
+     * horizontal or vertical mode.
+     */
+    private void setExtendEnd(CanvasProperty<Float> prop) {
+        if (isHorizontal()) {
+            mRightProp = prop;
+        } else {
+            mBottomProp = prop;
+        }
+    }
+
+    private CanvasProperty<Float> getExtendEnd() {
+        return isHorizontal() ? mRightProp : mBottomProp;
+    }
+
+    private int getExtendSize() {
+        return isHorizontal() ? getBounds().width() : getBounds().height();
+    }
+
+    private int getRippleSize() {
+        int size = isHorizontal() ? getBounds().width() : getBounds().height();
+        return Math.min(size, mMaxWidth);
+    }
+
+    private void enterHardware() {
+        endAnimations("enterHardware", true /* cancel */);
+        mVisible = true;
+        mDrawingHardwareGlow = true;
+        setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2));
+        final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(),
+                getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+        startAnim.setDuration(ANIMATION_DURATION_SCALE);
+        startAnim.setInterpolator(mInterpolator);
+        startAnim.addListener(mAnimatorListener);
+        startAnim.setTarget(mTargetView);
+
+        setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2));
+        final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(),
+                getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2);
+        endAnim.setDuration(ANIMATION_DURATION_SCALE);
+        endAnim.setInterpolator(mInterpolator);
+        endAnim.addListener(mAnimatorListener);
+        endAnim.addListener(mEnterHwTraceAnimator);
+        endAnim.setTarget(mTargetView);
+
+        if (isHorizontal()) {
+            mTopProp = CanvasProperty.createFloat(0f);
+            mBottomProp = CanvasProperty.createFloat(getBounds().height());
+            mRxProp = CanvasProperty.createFloat(getBounds().height()/2);
+            mRyProp = CanvasProperty.createFloat(getBounds().height()/2);
+        } else {
+            mLeftProp = CanvasProperty.createFloat(0f);
+            mRightProp = CanvasProperty.createFloat(getBounds().width());
+            mRxProp = CanvasProperty.createFloat(getBounds().width()/2);
+            mRyProp = CanvasProperty.createFloat(getBounds().width()/2);
+        }
+
+        mGlowScale = GLOW_MAX_SCALE_FACTOR;
+        mGlowAlpha = getMaxGlowAlpha();
+        mRipplePaint = getRipplePaint();
+        mRipplePaint.setAlpha((int) (mGlowAlpha * 255));
+        mPaintProp = CanvasProperty.createPaint(mRipplePaint);
+
+        startAnim.start();
+        endAnim.start();
+        mRunningAnimations.add(startAnim);
+        mRunningAnimations.add(endAnim);
+
+        invalidateSelf();
+
+        // With the delay, it could eventually animate the enter animation with no pressed state,
+        // then immediately show the exit animation. If this is skipped there will be no ripple.
+        if (mDelayTouchFeedback && !mPressed) {
+            exitHardware();
+        }
+    }
+
+    private void exitHardware() {
+        mPaintProp = CanvasProperty.createPaint(getRipplePaint());
+        final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp,
+                RenderNodeAnimator.PAINT_ALPHA, 0);
+        opacityAnim.setDuration(ANIMATION_DURATION_FADE);
+        opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR);
+        opacityAnim.addListener(mAnimatorListener);
+        opacityAnim.addListener(mExitHwTraceAnimator);
+        opacityAnim.setTarget(mTargetView);
+
+        opacityAnim.start();
+        mRunningAnimations.add(opacityAnim);
+
+        invalidateSelf();
+    }
+
+    private final AnimatorListenerAdapter mAnimatorListener =
+            new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRunningAnimations.remove(animation);
+                    if (mRunningAnimations.isEmpty() && !mPressed) {
+                        mVisible = false;
+                        mDrawingHardwareGlow = false;
+                        invalidateSelf();
+                    }
+                }
+            };
+
+    private static final class TraceAnimatorListener extends AnimatorListenerAdapter {
+        private final String mName;
+        TraceAnimatorListener(String name) {
+            mName = name;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.start." + mName);
+            Trace.endSection();
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.cancel." + mName);
+            Trace.endSection();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            Trace.beginSection("KeyButtonRipple.end." + mName);
+            Trace.endSection();
+        }
+    }
+
+    /**
+     * Interpolator with a smooth log deceleration
+     */
+    private static final class LogInterpolator implements Interpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return 1 - (float) Math.pow(400, -input * 1.4);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
new file mode 100644
index 0000000..74d30f8
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * @hide
+ */
+public class KeyButtonView extends ImageView implements ButtonInterface {
+    private static final String TAG = KeyButtonView.class.getSimpleName();
+
+    private final boolean mPlaySounds;
+    private long mDownTime;
+    private boolean mTracking;
+    private int mCode;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private AudioManager mAudioManager;
+    private boolean mGestureAborted;
+    @VisibleForTesting boolean mLongClicked;
+    private OnClickListener mOnClickListener;
+    private final KeyButtonRipple mRipple;
+    private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private float mDarkIntensity;
+    private boolean mHasOvalBg = false;
+
+    private final Runnable mCheckLongPress = new Runnable() {
+        public void run() {
+            if (isPressed()) {
+                // Log.d("KeyButtonView", "longpressed: " + this);
+                if (isLongClickable()) {
+                    // Just an old-fashioned ImageView
+                    performLongClick();
+                    mLongClicked = true;
+                } else {
+                    if (mCode != KEYCODE_UNKNOWN) {
+                        sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+                    }
+                    mLongClicked = true;
+                }
+            }
+        }
+    };
+
+    public KeyButtonView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // TODO(b/205803355): Figure out better place to set this.
+        switch (getId()) {
+            case com.android.internal.R.id.input_method_nav_back:
+                mCode = KEYCODE_BACK;
+                break;
+            default:
+                mCode = KEYCODE_UNKNOWN;
+                break;
+        }
+
+        mPlaySounds = true;
+
+        setClickable(true);
+        mAudioManager = context.getSystemService(AudioManager.class);
+
+        mRipple = new KeyButtonRipple(context, this,
+                com.android.internal.R.dimen.input_method_nav_key_button_ripple_max_width);
+        setBackground(mRipple);
+        setWillNotDraw(false);
+        forceHasOverlappingRendering(false);
+    }
+
+    @Override
+    public boolean isClickable() {
+        return mCode != KEYCODE_UNKNOWN || super.isClickable();
+    }
+
+    public void setCode(int code) {
+        mCode = code;
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener onClickListener) {
+        super.setOnClickListener(onClickListener);
+        mOnClickListener = onClickListener;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (mCode != KEYCODE_UNKNOWN) {
+            info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
+            if (isLongClickable()) {
+                info.addAction(
+                        new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+            }
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        if (visibility != View.VISIBLE) {
+            jumpDrawablesToCurrentState();
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        if (action == ACTION_CLICK && mCode != KEYCODE_UNKNOWN) {
+            sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
+            sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+            mTracking = false;
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+            playSoundEffect(SoundEffectConstants.CLICK);
+            return true;
+        } else if (action == ACTION_LONG_CLICK && mCode != KEYCODE_UNKNOWN) {
+            sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+            sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+            mTracking = false;
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+            return true;
+        }
+        return super.performAccessibilityActionInternal(action, arguments);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        final boolean showSwipeUI = false; // mOverviewProxyService.shouldShowSwipeUpUI();
+        final int action = ev.getAction();
+        int x, y;
+        if (action == MotionEvent.ACTION_DOWN) {
+            mGestureAborted = false;
+        }
+        if (mGestureAborted) {
+            setPressed(false);
+            return false;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mDownTime = SystemClock.uptimeMillis();
+                mLongClicked = false;
+                setPressed(true);
+
+                // Use raw X and Y to detect gestures in case a parent changes the x and y values
+                mTouchDownX = (int) ev.getRawX();
+                mTouchDownY = (int) ev.getRawY();
+                if (mCode != KEYCODE_UNKNOWN) {
+                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
+                } else {
+                    // Provide the same haptic feedback that the system offers for virtual keys.
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                }
+                if (!showSwipeUI) {
+                    playSoundEffect(SoundEffectConstants.CLICK);
+                }
+                removeCallbacks(mCheckLongPress);
+                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+                break;
+            case MotionEvent.ACTION_MOVE:
+                x = (int)ev.getRawX();
+                y = (int)ev.getRawY();
+
+                float slop = getQuickStepTouchSlopPx(getContext());
+                if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) {
+                    // When quick step is enabled, prevent animating the ripple triggered by
+                    // setPressed and decide to run it on touch up
+                    setPressed(false);
+                    removeCallbacks(mCheckLongPress);
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                setPressed(false);
+                if (mCode != KEYCODE_UNKNOWN) {
+                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+                }
+                removeCallbacks(mCheckLongPress);
+                break;
+            case MotionEvent.ACTION_UP:
+                final boolean doIt = isPressed() && !mLongClicked;
+                setPressed(false);
+                final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150;
+                if (showSwipeUI) {
+                    if (doIt) {
+                        // Apply haptic feedback on touch up since there is none on touch down
+                        performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+                        playSoundEffect(SoundEffectConstants.CLICK);
+                    }
+                } else if (doHapticFeedback && !mLongClicked) {
+                    // Always send a release ourselves because it doesn't seem to be sent elsewhere
+                    // and it feels weird to sometimes get a release haptic and other times not.
+                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE);
+                }
+                if (mCode != KEYCODE_UNKNOWN) {
+                    if (doIt) {
+                        sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0);
+                        mTracking = false;
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+                    } else {
+                        sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+                    }
+                } else {
+                    // no key code, just a regular ImageView
+                    if (doIt && mOnClickListener != null) {
+                        mOnClickListener.onClick(this);
+                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+                    }
+                }
+                removeCallbacks(mCheckLongPress);
+                break;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        super.setImageDrawable(drawable);
+
+        if (drawable == null) {
+            return;
+        }
+        KeyButtonDrawable keyButtonDrawable = (KeyButtonDrawable) drawable;
+        keyButtonDrawable.setDarkIntensity(mDarkIntensity);
+        mHasOvalBg = keyButtonDrawable.hasOvalBg();
+        if (mHasOvalBg) {
+            mOvalBgPaint.setColor(keyButtonDrawable.getDrawableBackgroundColor());
+        }
+        mRipple.setType(keyButtonDrawable.hasOvalBg() ? KeyButtonRipple.Type.OVAL
+                : KeyButtonRipple.Type.ROUNDED_RECT);
+    }
+
+    public void playSoundEffect(int soundConstant) {
+        if (!mPlaySounds) return;
+        mAudioManager.playSoundEffect(soundConstant);
+    }
+
+    public void sendEvent(int action, int flags) {
+        sendEvent(action, flags, SystemClock.uptimeMillis());
+    }
+
+    private void sendEvent(int action, int flags, long when) {
+        if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
+            if (action == MotionEvent.ACTION_UP) {
+                // TODO(b/205803355): Implement notifyBackAction();
+            }
+        }
+
+        // TODO(b/205803355): Consolidate this logic to somewhere else.
+        if (mContext instanceof InputMethodService) {
+            final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
+            final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
+                    0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+                    flags | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                    InputDevice.SOURCE_KEYBOARD);
+            int displayId = INVALID_DISPLAY;
+
+            // Make KeyEvent work on multi-display environment
+            if (getDisplay() != null) {
+                displayId = getDisplay().getDisplayId();
+            }
+            if (displayId != INVALID_DISPLAY) {
+                ev.setDisplayId(displayId);
+            }
+            final InputMethodService ims = (InputMethodService) mContext;
+            final boolean handled;
+            switch (action) {
+                case KeyEvent.ACTION_DOWN:
+                    handled = ims.onKeyDown(ev.getKeyCode(), ev);
+                    mTracking = handled && ev.getRepeatCount() == 0 &&
+                            (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+                    break;
+                case KeyEvent.ACTION_UP:
+                    handled = ims.onKeyUp(ev.getKeyCode(), ev);
+                    break;
+                default:
+                    handled = false;
+                    break;
+            }
+            if (!handled) {
+                final InputConnection ic = ims.getCurrentInputConnection();
+                if (ic != null) {
+                    ic.sendKeyEvent(ev);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setDarkIntensity(float darkIntensity) {
+        mDarkIntensity = darkIntensity;
+
+        Drawable drawable = getDrawable();
+        if (drawable != null) {
+            ((KeyButtonDrawable) drawable).setDarkIntensity(darkIntensity);
+            // Since we reuse the same drawable for multiple views, we need to invalidate the view
+            // manually.
+            invalidate();
+        }
+        mRipple.setDarkIntensity(darkIntensity);
+    }
+
+    @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+        mRipple.setDelayTouchFeedback(shouldDelay);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mHasOvalBg) {
+            int d = Math.min(getWidth(), getHeight());
+            canvas.drawOval(0, 0, d, d, mOvalBgPaint);
+        }
+        super.draw(canvas);
+    }
+
+    /**
+     * Ratio of quickstep touch slop (when system takes over the touch) to view touch slop
+     */
+    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
+
+    /**
+     * Touch slop for quickstep gesture
+     */
+    private static float getQuickStepTouchSlopPx(Context context) {
+        return QUICKSTEP_TOUCH_SLOP_RATIO * ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
new file mode 100644
index 0000000..93c5439
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.annotation.ColorInt;
+
+final class NavigationBarConstants {
+    private NavigationBarConstants() {
+        // Not intended to be instantiated.
+    }
+
+    // Copied from "navbar_back_button_ime_offset"
+    // TODO(b/215443343): Handle this in the drawable then remove this constant.
+    static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f;
+
+    // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+    @ColorInt
+    static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff;
+
+    // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml
+    @ColorInt
+    static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000;
+
+    // Copied from "navigation_bar_deadzone_hold"
+    static final int NAVIGATION_BAR_DEADZONE_HOLD = 333;
+
+    // Copied from "navigation_bar_deadzone_hold"
+    static final int NAVIGATION_BAR_DEADZONE_DECAY = 333;
+
+    // Copied from "navigation_bar_deadzone_size"
+    static final float NAVIGATION_BAR_DEADZONE_SIZE = 12.0f;
+
+    // Copied from "navigation_bar_deadzone_size_max"
+    static final float NAVIGATION_BAR_DEADZONE_SIZE_MAX = 32.0f;
+
+    // Copied from "nav_key_button_shadow_offset_x"
+    static final float NAV_KEY_BUTTON_SHADOW_OFFSET_X = 0.0f;
+
+    // Copied from "nav_key_button_shadow_offset_y"
+    static final float NAV_KEY_BUTTON_SHADOW_OFFSET_Y = 1.0f;
+
+    // Copied from "nav_key_button_shadow_radius"
+    static final float NAV_KEY_BUTTON_SHADOW_RADIUS = 0.5f;
+
+    // Copied from "nav_key_button_shadow_color"
+    @ColorInt
+    static final int NAV_KEY_BUTTON_SHADOW_COLOR = 0x30000000;
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
new file mode 100644
index 0000000..f01173e
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.view.MotionEvent.ACTION_OUTSIDE;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public final class NavigationBarFrame extends FrameLayout {
+
+    private DeadZone mDeadZone = null;
+
+    public NavigationBarFrame(@NonNull Context context) {
+        super(context);
+    }
+
+    public NavigationBarFrame(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public void setDeadZone(@NonNull DeadZone deadZone) {
+        mDeadZone = deadZone;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (event.getAction() == ACTION_OUTSIDE) {
+            if (mDeadZone != null) {
+                return mDeadZone.onTouchEvent(event);
+            }
+        }
+        return super.dispatchTouchEvent(event);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
new file mode 100644
index 0000000..d488890
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.inputmethodservice.navigationbar.ReverseLinearLayout.ReverseRelativeLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Space;
+
+/**
+ * @hide
+ */
+public final class NavigationBarInflaterView extends FrameLayout {
+
+    private static final String TAG = "NavBarInflater";
+
+    public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+    public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+    public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
+
+    public static final String MENU_IME_ROTATE = "menu_ime";
+    public static final String BACK = "back";
+    public static final String HOME = "home";
+    public static final String RECENT = "recent";
+    public static final String NAVSPACE = "space";
+    public static final String CLIPBOARD = "clipboard";
+    public static final String HOME_HANDLE = "home_handle";
+    public static final String KEY = "key";
+    public static final String LEFT = "left";
+    public static final String RIGHT = "right";
+    public static final String CONTEXTUAL = "contextual";
+    public static final String IME_SWITCHER = "ime_switcher";
+
+    public static final String GRAVITY_SEPARATOR = ";";
+    public static final String BUTTON_SEPARATOR = ",";
+
+    public static final String SIZE_MOD_START = "[";
+    public static final String SIZE_MOD_END = "]";
+
+    public static final String KEY_CODE_START = "(";
+    public static final String KEY_IMAGE_DELIM = ":";
+    public static final String KEY_CODE_END = ")";
+    private static final String WEIGHT_SUFFIX = "W";
+    private static final String WEIGHT_CENTERED_SUFFIX = "WC";
+    private static final String ABSOLUTE_SUFFIX = "A";
+    private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C";
+
+    // Copied from "config_navBarLayoutHandle:
+    private static final String CONFIG_NAV_BAR_LAYOUT_HANDLE =
+            "back[70AC];home_handle;ime_switcher[70AC]";
+
+    protected LayoutInflater mLayoutInflater;
+    protected LayoutInflater mLandscapeInflater;
+
+    protected FrameLayout mHorizontal;
+
+    SparseArray<ButtonDispatcher> mButtonDispatchers;
+
+    private View mLastPortrait;
+    private View mLastLandscape;
+
+    private boolean mAlternativeOrder;
+
+    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        createInflaters();
+    }
+
+    void createInflaters() {
+        mLayoutInflater = LayoutInflater.from(mContext);
+        Configuration landscape = new Configuration();
+        landscape.setTo(mContext.getResources().getConfiguration());
+        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        inflateChildren();
+        clearViews();
+        inflateLayout(getDefaultLayout());
+    }
+
+    private void inflateChildren() {
+        removeAllViews();
+        mHorizontal = (FrameLayout) mLayoutInflater.inflate(
+                com.android.internal.R.layout.input_method_navigation_layout,
+                this /* root */, false /* attachToRoot */);
+        addView(mHorizontal);
+        updateAlternativeOrder();
+    }
+
+    String getDefaultLayout() {
+        return CONFIG_NAV_BAR_LAYOUT_HANDLE;
+    }
+
+    public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) {
+        mButtonDispatchers = buttonDispatchers;
+        for (int i = 0; i < buttonDispatchers.size(); i++) {
+            initiallyFill(buttonDispatchers.valueAt(i));
+        }
+    }
+
+    void updateButtonDispatchersCurrentView() {
+        if (mButtonDispatchers != null) {
+            View view = mHorizontal;
+            for (int i = 0; i < mButtonDispatchers.size(); i++) {
+                final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i);
+                dispatcher.setCurrentView(view);
+            }
+        }
+    }
+
+    void setAlternativeOrder(boolean alternativeOrder) {
+        if (alternativeOrder != mAlternativeOrder) {
+            mAlternativeOrder = alternativeOrder;
+            updateAlternativeOrder();
+        }
+    }
+
+    private void updateAlternativeOrder() {
+        updateAlternativeOrder(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+        updateAlternativeOrder(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_center_group));
+    }
+
+    private void updateAlternativeOrder(View v) {
+        if (v instanceof ReverseLinearLayout) {
+            ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder);
+        }
+    }
+
+    private void initiallyFill(
+            ButtonDispatcher buttonDispatcher) {
+        addAll(buttonDispatcher, mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+        addAll(buttonDispatcher, mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_center_group));
+    }
+
+    private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) {
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            // Need to manually search for each id, just in case each group has more than one
+            // of a single id.  It probably mostly a waste of time, but shouldn't take long
+            // and will only happen once.
+            if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) {
+                buttonDispatcher.addView(parent.getChildAt(i));
+            }
+            if (parent.getChildAt(i) instanceof ViewGroup) {
+                addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i));
+            }
+        }
+    }
+
+    protected void inflateLayout(String newLayout) {
+        if (newLayout == null) {
+            newLayout = getDefaultLayout();
+        }
+        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+        if (sets.length != 3) {
+            Log.d(TAG, "Invalid layout.");
+            newLayout = getDefaultLayout();
+            sets = newLayout.split(GRAVITY_SEPARATOR, 3);
+        }
+        String[] start = sets[0].split(BUTTON_SEPARATOR);
+        String[] center = sets[1].split(BUTTON_SEPARATOR);
+        String[] end = sets[2].split(BUTTON_SEPARATOR);
+        // Inflate these in start to end order or accessibility traversal will be messed up.
+        inflateButtons(start, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_ends_group),
+                false /* landscape */, true /* start */);
+
+        inflateButtons(center, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_center_group),
+                false /* landscape */, false /* start */);
+
+        addGravitySpacer(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_ends_group));
+
+        inflateButtons(end, mHorizontal.findViewById(
+                        com.android.internal.R.id.input_method_nav_ends_group),
+                false /* landscape */, false /* start */);
+
+        updateButtonDispatchersCurrentView();
+    }
+
+    private void addGravitySpacer(LinearLayout layout) {
+        layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
+    }
+
+    private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
+            boolean start) {
+        for (int i = 0; i < buttons.length; i++) {
+            inflateButton(buttons[i], parent, landscape, start);
+        }
+    }
+
+    private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
+        if (layoutParams instanceof LinearLayout.LayoutParams) {
+            return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
+                    ((LinearLayout.LayoutParams) layoutParams).weight);
+        }
+        return new LayoutParams(layoutParams.width, layoutParams.height);
+    }
+
+    @Nullable
+    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
+            boolean start) {
+        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
+        View v = createView(buttonSpec, parent, inflater);
+        if (v == null) return null;
+
+        v = applySize(v, buttonSpec, landscape, start);
+        parent.addView(v);
+        addToDispatchers(v);
+        View lastView = landscape ? mLastLandscape : mLastPortrait;
+        View accessibilityView = v;
+        if (v instanceof ReverseRelativeLayout) {
+            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
+        }
+        if (lastView != null) {
+            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
+        }
+        if (landscape) {
+            mLastLandscape = accessibilityView;
+        } else {
+            mLastPortrait = accessibilityView;
+        }
+        return v;
+    }
+
+    private View applySize(View v, String buttonSpec, boolean landscape, boolean start) {
+        String sizeStr = extractSize(buttonSpec);
+        if (sizeStr == null) return v;
+
+        if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) {
+            // To support gravity, wrap in RelativeLayout and apply gravity to it.
+            // Children wanting to use gravity must be smaller than the frame.
+            ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext);
+            LayoutParams childParams = new LayoutParams(v.getLayoutParams());
+
+            // Compute gravity to apply
+            int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM)
+                    : (start ? Gravity.START : Gravity.END);
+            if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
+                gravity = Gravity.CENTER;
+            } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) {
+                gravity = Gravity.CENTER_VERTICAL;
+            }
+
+            // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR)
+            frame.setDefaultGravity(gravity);
+            frame.setGravity(gravity); // Apply gravity to root
+
+            frame.addView(v, childParams);
+
+            if (sizeStr.contains(WEIGHT_SUFFIX)) {
+                // Use weighting to set the width of the frame
+                float weight = Float.parseFloat(
+                        sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
+                frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight));
+            } else {
+                int width = (int) convertDpToPx(mContext,
+                        Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX))));
+                frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT));
+            }
+
+            // Ensure ripples can be drawn outside bounds
+            frame.setClipChildren(false);
+            frame.setClipToPadding(false);
+
+            return frame;
+        }
+
+        float size = Float.parseFloat(sizeStr);
+        ViewGroup.LayoutParams params = v.getLayoutParams();
+        params.width = (int) (params.width * size);
+        return v;
+    }
+
+    View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
+        View v = null;
+        String button = extractButton(buttonSpec);
+        if (LEFT.equals(button)) {
+            button = extractButton(NAVSPACE);
+        } else if (RIGHT.equals(button)) {
+            button = extractButton(MENU_IME_ROTATE);
+        }
+        if (HOME.equals(button)) {
+            //v = inflater.inflate(R.layout.home, parent, false);
+        } else if (BACK.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_back, parent,
+                    false);
+        } else if (RECENT.equals(button)) {
+            //v = inflater.inflate(R.layout.recent_apps, parent, false);
+        } else if (MENU_IME_ROTATE.equals(button)) {
+            //v = inflater.inflate(R.layout.menu_ime, parent, false);
+        } else if (NAVSPACE.equals(button)) {
+            //v = inflater.inflate(R.layout.nav_key_space, parent, false);
+        } else if (CLIPBOARD.equals(button)) {
+            //v = inflater.inflate(R.layout.clipboard, parent, false);
+        } else if (CONTEXTUAL.equals(button)) {
+            //v = inflater.inflate(R.layout.contextual, parent, false);
+        } else if (HOME_HANDLE.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_home_handle,
+                    parent, false);
+        } else if (IME_SWITCHER.equals(button)) {
+            v = inflater.inflate(com.android.internal.R.layout.input_method_nav_ime_switcher,
+                    parent, false);
+        } else if (button.startsWith(KEY)) {
+            /*
+            String uri = extractImage(button);
+            int code = extractKeycode(button);
+            v = inflater.inflate(R.layout.custom_key, parent, false);
+            ((KeyButtonView) v).setCode(code);
+            if (uri != null) {
+                if (uri.contains(":")) {
+                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+                } else if (uri.contains("/")) {
+                    int index = uri.indexOf('/');
+                    String pkg = uri.substring(0, index);
+                    int id = Integer.parseInt(uri.substring(index + 1));
+                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+                }
+            }
+         */
+        }
+        return v;
+    }
+
+    /*
+    public static String extractImage(String buttonSpec) {
+        if (!buttonSpec.contains(KEY_IMAGE_DELIM)) {
+            return null;
+        }
+        final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM);
+        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END));
+        return subStr;
+    }
+
+    public static int extractKeycode(String buttonSpec) {
+        if (!buttonSpec.contains(KEY_CODE_START)) {
+            return 1;
+        }
+        final int start = buttonSpec.indexOf(KEY_CODE_START);
+        String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM));
+        return Integer.parseInt(subStr);
+    }
+    */
+
+    public static String extractSize(String buttonSpec) {
+        if (!buttonSpec.contains(SIZE_MOD_START)) {
+            return null;
+        }
+        final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START);
+        return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END));
+    }
+
+    public static String extractButton(String buttonSpec) {
+        if (!buttonSpec.contains(SIZE_MOD_START)) {
+            return buttonSpec;
+        }
+        return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
+    }
+
+    private void addToDispatchers(View v) {
+        if (mButtonDispatchers != null) {
+            final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
+            if (indexOfKey >= 0) {
+                mButtonDispatchers.valueAt(indexOfKey).addView(v);
+            }
+            if (v instanceof ViewGroup) {
+                final ViewGroup viewGroup = (ViewGroup)v;
+                final int N = viewGroup.getChildCount();
+                for (int i = 0; i < N; i++) {
+                    addToDispatchers(viewGroup.getChildAt(i));
+                }
+            }
+        }
+    }
+
+    private void clearViews() {
+        if (mButtonDispatchers != null) {
+            for (int i = 0; i < mButtonDispatchers.size(); i++) {
+                mButtonDispatchers.valueAt(i).clear();
+            }
+        }
+        clearAllChildren(mHorizontal.findViewById(
+                com.android.internal.R.id.input_method_nav_buttons));
+    }
+
+    private void clearAllChildren(ViewGroup group) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            ((ViewGroup) group.getChildAt(i)).removeAllViews();
+        }
+    }
+
+    private static float convertDpToPx(Context context, float dp) {
+        return dp * context.getResources().getDisplayMetrics().density;
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
new file mode 100644
index 0000000..c6096d7
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+final class NavigationBarUtils {
+    private NavigationBarUtils() {
+        // Not intended to be instantiated.
+    }
+
+    /**
+     * A utility method to convert "dp" to "pixel".
+     *
+     * <p>TODO(b/215443343): Remove this method by migrating DP values from
+     * {@link NavigationBarConstants} to resource files.</p>
+     *
+     * @param dpValue "dp" value to be converted to "pixel"
+     * @param res {@link Resources} to be used when dealing with "dp".
+     * @return the pixels for a given dp value.
+     */
+    static int dpToPx(float dpValue, Resources res) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, res.getDisplayMetrics());
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
new file mode 100644
index 0000000..4284778
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
+import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
+
+import java.util.function.Consumer;
+
+/**
+ * @hide
+ */
+public final class NavigationBarView extends FrameLayout {
+    final static boolean DEBUG = false;
+    final static String TAG = "NavBarView";
+
+    // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN
+    private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+    // The current view is always mHorizontal.
+    View mCurrentView = null;
+    private View mHorizontal;
+
+    private int mCurrentRotation = -1;
+
+    int mDisabledFlags = 0;
+    int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+    private final int mNavBarMode = NAV_BAR_MODE_GESTURAL;
+
+    private KeyButtonDrawable mBackIcon;
+    private KeyButtonDrawable mImeSwitcherIcon;
+    private Context mLightContext;
+    private final int mLightIconColor;
+    private final int mDarkIconColor;
+
+    private final android.inputmethodservice.navigationbar.DeadZone mDeadZone;
+    private boolean mDeadZoneConsuming = false;
+
+    private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
+    private Configuration mConfiguration;
+    private Configuration mTmpLastConfiguration;
+
+    private NavigationBarInflaterView mNavigationInflaterView;
+
+    public NavigationBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mLightContext = context;
+        mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
+        mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
+
+        mConfiguration = new Configuration();
+        mTmpLastConfiguration = new Configuration();
+        mConfiguration.updateFrom(context.getResources().getConfiguration());
+
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back));
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher));
+        mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle,
+                new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
+
+        mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
+
+        getImeSwitchButton().setOnClickListener(view -> view.getContext()
+                .getSystemService(InputMethodManager.class).showInputMethodPicker());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        shouldDeadZoneConsumeTouchEvents(event);
+        return super.onTouchEvent(event);
+    }
+
+    private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
+        int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mDeadZoneConsuming = false;
+        }
+        if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
+            switch (action) {
+                case MotionEvent.ACTION_DOWN:
+                    mDeadZoneConsuming = true;
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    mDeadZoneConsuming = false;
+                    break;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public View getCurrentView() {
+        return mCurrentView;
+    }
+
+    /**
+     * Applies {@param consumer} to each of the nav bar views.
+     */
+    public void forEachView(Consumer<View> consumer) {
+        if (mHorizontal != null) {
+            consumer.accept(mHorizontal);
+        }
+    }
+
+    public ButtonDispatcher getBackButton() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back);
+    }
+
+    public ButtonDispatcher getImeSwitchButton() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher);
+    }
+
+    public ButtonDispatcher getHomeHandle() {
+        return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle);
+    }
+
+    public SparseArray<ButtonDispatcher> getButtonDispatchers() {
+        return mButtonDispatchers;
+    }
+
+    private void reloadNavIcons() {
+        updateIcons(Configuration.EMPTY);
+    }
+
+    private void updateIcons(Configuration oldConfig) {
+        final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
+        final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
+        final boolean dirChange =
+                oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
+
+        if (densityChange || dirChange) {
+            mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher);
+        }
+        if (orientationChange || densityChange || dirChange) {
+            mBackIcon = getBackDrawable();
+        }
+    }
+
+    public KeyButtonDrawable getBackDrawable() {
+        KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back);
+        orientBackButton(drawable);
+        return drawable;
+    }
+
+    /**
+     * @return whether this nav bar mode is edge to edge
+     */
+    public static boolean isGesturalMode(int mode) {
+        return mode == NAV_BAR_MODE_GESTURAL;
+    }
+
+    private void orientBackButton(KeyButtonDrawable drawable) {
+        final boolean useAltBack =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+        float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
+        if (drawable.getRotation() == degrees) {
+            return;
+        }
+
+        if (isGesturalMode(mNavBarMode)) {
+            drawable.setRotation(degrees);
+            return;
+        }
+
+        // Animate the back button's rotation to the new degrees and only in portrait move up the
+        // back button to line up with the other buttons
+        float targetY = useAltBack
+                ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
+                : 0;
+        ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
+                PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
+                PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
+        navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+        navBarAnimator.setDuration(200);
+        navBarAnimator.start();
+    }
+
+    private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
+        return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon,
+                true /* hasShadow */, null /* ovalBackgroundColor */);
+    }
+
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        reloadNavIcons();
+
+        super.setLayoutDirection(layoutDirection);
+    }
+
+    public void setNavigationIconHints(int hints) {
+        if (hints == mNavigationIconHints) return;
+        final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        final boolean oldBackAlt =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
+        if (newBackAlt != oldBackAlt) {
+            //onImeVisibilityChanged(newBackAlt);
+        }
+
+        if (DEBUG) {
+            android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500)
+                    .show();
+        }
+        mNavigationIconHints = hints;
+        updateNavButtonIcons();
+    }
+
+    public void setDisabledFlags(int disabledFlags) {
+        if (mDisabledFlags == disabledFlags) return;
+
+        mDisabledFlags = disabledFlags;
+
+        updateNavButtonIcons();
+    }
+
+    public void updateNavButtonIcons() {
+        // We have to replace or restore the back and home button icons when exiting or entering
+        // carmode, respectively. Recents are not available in CarMode in nav bar so change
+        // to recent icon is not required.
+        KeyButtonDrawable backIcon = mBackIcon;
+        orientBackButton(backIcon);
+        getBackButton().setImageDrawable(backIcon);
+
+        getImeSwitchButton().setImageDrawable(mImeSwitcherIcon);
+
+        // Update IME button visibility, a11y and rotate button always overrides the appearance
+        final boolean imeSwitcherVisible =
+                (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
+        getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE);
+
+        getBackButton().setVisibility(View.VISIBLE);
+        getHomeHandle().setVisibility(View.INVISIBLE);
+
+        // We used to be reporting the touch regions via notifyActiveTouchRegions() here.
+        // TODO(b/215593010): Consider taking care of this in the Launcher side.
+    }
+
+    private Display getContextDisplay() {
+        return getContext().getDisplay();
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater);
+        mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
+
+        updateOrientationViews();
+        reloadNavIcons();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mDeadZone.onDraw(canvas);
+        super.onDraw(canvas);
+    }
+
+    private void updateOrientationViews() {
+        mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal);
+
+        updateCurrentView();
+    }
+
+    private void updateCurrentView() {
+        resetViews();
+        mCurrentView = mHorizontal;
+        mCurrentView.setVisibility(View.VISIBLE);
+        mCurrentRotation = getContextDisplay().getRotation();
+        mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
+        mNavigationInflaterView.updateButtonDispatchersCurrentView();
+    }
+
+    private void resetViews() {
+        mHorizontal.setVisibility(View.GONE);
+    }
+
+    public void reorient() {
+        updateCurrentView();
+
+        final android.inputmethodservice.navigationbar.NavigationBarFrame frame =
+                getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame);
+        frame.setDeadZone(mDeadZone);
+        mDeadZone.onConfigurationChanged(mCurrentRotation);
+
+        if (DEBUG) {
+            Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
+        }
+
+        // Resolve layout direction if not resolved since components changing layout direction such
+        // as changing languages will recreate this view and the direction will be resolved later
+        if (!isLayoutDirectionResolved()) {
+            resolveLayoutDirection();
+        }
+        updateNavButtonIcons();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mTmpLastConfiguration.updateFrom(mConfiguration);
+        final int changes = mConfiguration.updateFrom(newConfig);
+
+        updateIcons(mTmpLastConfiguration);
+        if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
+                || mTmpLastConfiguration.getLayoutDirection()
+                        != mConfiguration.getLayoutDirection()) {
+            // If car mode or density changes, we need to reset the icons.
+            updateNavButtonIcons();
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        // This needs to happen first as it can changed the enabled state which can affect whether
+        // the back button is visible
+        requestApplyInsets();
+        reorient();
+        updateNavButtonIcons();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+            mButtonDispatchers.valueAt(i).onDestroy();
+        }
+    }
+
+    public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) {
+        for (int i = 0; i < mButtonDispatchers.size(); ++i) {
+            mButtonDispatchers.valueAt(i).setDarkIntensity(intensity);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
new file mode 100644
index 0000000..273cafb
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * TODO(b/215443343): Remove this file, as IME actually doesn't use this.
+ *
+ * @hide
+ */
+public class NavigationHandle extends View implements ButtonInterface {
+
+    public NavigationHandle(Context context) {
+        this(context, null);
+    }
+
+    public NavigationHandle(Context context, AttributeSet attr) {
+        super(context, attr);
+        setFocusable(false);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return false;
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+    }
+
+    @Override
+    public void setDarkIntensity(float intensity) {
+    }
+
+    @Override
+    public void setDelayTouchFeedback(boolean shouldDelay) {
+    }
+}
diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
new file mode 100644
index 0000000..68163c3
--- /dev/null
+++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 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.inputmethodservice.navigationbar;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+
+/**
+ * Automatically reverses the order of children as they are added.
+ * Also reverse the width and height values of layout params
+ * @hide
+ */
+public class ReverseLinearLayout extends LinearLayout {
+
+    /** If true, the layout is reversed vs. a regular linear layout */
+    private boolean mIsLayoutReverse;
+
+    /** If true, the layout is opposite to it's natural reversity from the layout direction */
+    private boolean mIsAlternativeOrder;
+
+    public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        updateOrder();
+    }
+
+    @Override
+    public void addView(View child) {
+        reverseParams(child.getLayoutParams(), child, mIsLayoutReverse);
+        if (mIsLayoutReverse) {
+            super.addView(child, 0);
+        } else {
+            super.addView(child);
+        }
+    }
+
+    @Override
+    public void addView(View child, ViewGroup.LayoutParams params) {
+        reverseParams(params, child, mIsLayoutReverse);
+        if (mIsLayoutReverse) {
+            super.addView(child, 0, params);
+        } else {
+            super.addView(child, params);
+        }
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        super.onRtlPropertiesChanged(layoutDirection);
+        updateOrder();
+    }
+
+    public void setAlternativeOrder(boolean alternative) {
+        mIsAlternativeOrder = alternative;
+        updateOrder();
+    }
+
+    /**
+     * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we
+     * have to do it manually
+     */
+    private void updateOrder() {
+        boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder;
+
+        if (mIsLayoutReverse != isLayoutReverse) {
+            // reversity changed, swap the order of all views.
+            int childCount = getChildCount();
+            ArrayList<View> childList = new ArrayList<>(childCount);
+            for (int i = 0; i < childCount; i++) {
+                childList.add(getChildAt(i));
+            }
+            removeAllViews();
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View child = childList.get(i);
+                super.addView(child);
+            }
+            mIsLayoutReverse = isLayoutReverse;
+        }
+    }
+
+    private static void reverseParams(ViewGroup.LayoutParams params, View child,
+            boolean isLayoutReverse) {
+        if (child instanceof Reversible) {
+            ((Reversible) child).reverse(isLayoutReverse);
+        }
+        if (child.getPaddingLeft() == child.getPaddingRight()
+                && child.getPaddingTop() == child.getPaddingBottom()) {
+            child.setPadding(child.getPaddingTop(), child.getPaddingLeft(),
+                    child.getPaddingTop(), child.getPaddingLeft());
+        }
+        if (params == null) {
+            return;
+        }
+        int width = params.width;
+        params.width = params.height;
+        params.height = width;
+    }
+
+    interface Reversible {
+        void reverse(boolean isLayoutReverse);
+    }
+
+    public static class ReverseRelativeLayout extends RelativeLayout implements Reversible {
+
+        public ReverseRelativeLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void reverse(boolean isLayoutReverse) {
+            updateGravity(isLayoutReverse);
+            reverseGroup(this, isLayoutReverse);
+        }
+
+        private int mDefaultGravity = Gravity.NO_GRAVITY;
+        public void setDefaultGravity(int gravity) {
+            mDefaultGravity = gravity;
+        }
+
+        public void updateGravity(boolean isLayoutReverse) {
+            // Flip gravity if top of bottom is used
+            if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return;
+
+            // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise
+            int gravityToApply = mDefaultGravity;
+            if (isLayoutReverse) {
+                gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP;
+            }
+
+            if (getGravity() != gravityToApply) setGravity(gravityToApply);
+        }
+    }
+
+    private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) {
+        for (int i = 0; i < group.getChildCount(); i++) {
+            final View child = group.getChildAt(i);
+            reverseParams(child.getLayoutParams(), child, isLayoutReverse);
+
+            // Recursively reverse all children
+            if (child instanceof ViewGroup) {
+                reverseGroup((ViewGroup) child, isLayoutReverse);
+            }
+        }
+    }
+}
diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java
index d1f49d2..506cbcb 100644
--- a/core/java/android/net/LocalServerSocket.java
+++ b/core/java/android/net/LocalServerSocket.java
@@ -55,7 +55,9 @@
      * Create a LocalServerSocket from a file descriptor that's already
      * been created and bound. listen() will be called immediately on it.
      * Used for cases where file descriptors are passed in via environment
-     * variables
+     * variables. The passed-in FileDescriptor is not managed by this class
+     * and must be closed by the caller. Calling {@link #close()} on a socket
+     * created by this method has no effect.
      *
      * @param fd bound file descriptor
      * @throws IOException
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index 5b38f78..b69410c 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -16,7 +16,14 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import java.io.Closeable;
 import java.io.FileDescriptor;
@@ -74,32 +81,39 @@
         this.isBound = false;
     }
 
+    private void checkConnected() {
+        try {
+            Os.getpeername(impl.getFileDescriptor());
+        } catch (ErrnoException e) {
+            throw new IllegalArgumentException("Not a connected socket", e);
+        }
+        isConnected = true;
+        isBound = true;
+        implCreated = true;
+    }
+
     /**
-     * Creates a LocalSocket instances using the FileDescriptor for an already-connected
-     * AF_LOCAL/UNIX domain stream socket. Note: the FileDescriptor must be closed by the caller:
-     * closing the LocalSocket will not close it.
+     * Creates a LocalSocket instance using the {@link FileDescriptor} for an already-connected
+     * AF_LOCAL/UNIX domain stream socket. The passed-in FileDescriptor is not managed by this class
+     * and must be closed by the caller. Calling {@link #close()} on a socket created by this
+     * method has no effect.
      *
-     * @hide - used by BluetoothSocket.
+     * @param fd the filedescriptor to adopt
+     *
+     * @hide
      */
-    public static LocalSocket createConnectedLocalSocket(FileDescriptor fd) {
-        return createConnectedLocalSocket(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+    @SystemApi(client = MODULE_LIBRARIES)
+    public LocalSocket(@NonNull @SuppressLint("UseParcelFileDescriptor") FileDescriptor fd) {
+        this(new LocalSocketImpl(fd), SOCKET_UNKNOWN);
+        checkConnected();
     }
 
     /**
      * for use with LocalServerSocket.accept()
      */
     static LocalSocket createLocalSocketForAccept(LocalSocketImpl impl) {
-        return createConnectedLocalSocket(impl, SOCKET_UNKNOWN);
-    }
-
-    /**
-     * Creates a LocalSocket from an existing LocalSocketImpl that is already connected.
-     */
-    private static LocalSocket createConnectedLocalSocket(LocalSocketImpl impl, int sockType) {
-        LocalSocket socket = new LocalSocket(impl, sockType);
-        socket.isConnected = true;
-        socket.isBound = true;
-        socket.implCreated = true;
+        LocalSocket socket = new LocalSocket(impl, SOCKET_UNKNOWN);
+        socket.checkConnected();
         return socket;
     }
 
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index a6830b7..2339656 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -549,8 +549,8 @@
          * networks, a network will be chosen arbitrarily amongst the networks matching the highest
          * priority rule.
          *
-         * <p>If all networks fail to match the rules provided, an underlying network will still be
-         * selected (at random if necessary).
+         * <p>If all networks fail to match the rules provided, a carrier-owned underlying network
+         * will still be selected (if available, at random if necessary).
          *
          * @param underlyingNetworkTemplates a list of unique VcnUnderlyingNetworkTemplates that are
          *     ordered from most to least preferred, or an empty list to use the default
diff --git a/core/java/android/os/BluetoothServiceManager.java b/core/java/android/os/BluetoothServiceManager.java
new file mode 100644
index 0000000..12f7bc8
--- /dev/null
+++ b/core/java/android/os/BluetoothServiceManager.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.os.BluetoothServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the bluetooth
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class BluetoothServiceManager {
+
+    /** @hide */
+    public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+    /**
+     * @hide
+     */
+    public BluetoothServiceManager() {
+    }
+
+    /**
+     * A class that exposes the methods to register and obtain each system service.
+     */
+    public static final class ServiceRegisterer {
+        private final String mServiceName;
+
+        /**
+         * @hide
+         */
+        public ServiceRegisterer(String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Register a system server binding object for a service.
+         */
+        public void register(@NonNull IBinder service) {
+            ServiceManager.addService(mServiceName, service);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it returns null.
+         */
+        @Nullable
+        public IBinder get() {
+            return ServiceManager.getService(mServiceName);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+         */
+        @NonNull
+        public IBinder getOrThrow() throws ServiceNotFoundException {
+            try {
+                return ServiceManager.getServiceOrThrow(mServiceName);
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new ServiceNotFoundException(mServiceName);
+            }
+        }
+
+        /**
+         * Get the system server binding object for a service. If the specified service is
+         * not available, it returns null.
+         */
+        @Nullable
+        public IBinder tryGet() {
+            return ServiceManager.checkService(mServiceName);
+        }
+    }
+
+    /**
+     * See {@link ServiceRegisterer#getOrThrow}.
+     *
+     */
+    public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+        /**
+         * Constructor.
+         *
+         * @param name the name of the binder service that cannot be found.
+         *
+         */
+        public ServiceNotFoundException(@NonNull String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "bluetooth" service.
+     */
+    @NonNull
+    public ServiceRegisterer getBluetoothManagerServiceRegisterer() {
+        return new ServiceRegisterer(BLUETOOTH_MANAGER_SERVICE);
+    }
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d4a338b..1810904 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,6 +44,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.Duration;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -612,7 +613,7 @@
     public static final int WAKE_REASON_PLUGGED_IN = 3;
 
     /**
-     * Wake up reason code: Waking up due to a user performed gesture (e.g. douple tapping on the
+     * Wake up reason code: Waking up due to a user performed gesture (e.g. double tapping on the
      * screen).
      * @hide
      */
@@ -1013,7 +1014,7 @@
 
     private static final int MAX_CACHE_ENTRIES = 1;
 
-    private PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
+    private final PropertyInvalidatedCache<Void, Boolean> mPowerSaveModeCache =
             new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
                 CACHE_KEY_IS_POWER_SAVE_MODE_PROPERTY) {
                 @Override
@@ -1026,7 +1027,7 @@
                 }
             };
 
-    private PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
+    private final PropertyInvalidatedCache<Void, Boolean> mInteractiveCache =
             new PropertyInvalidatedCache<Void, Boolean>(MAX_CACHE_ENTRIES,
                 CACHE_KEY_IS_INTERACTIVE_PROPERTY) {
                 @Override
@@ -1047,7 +1048,7 @@
     final IThermalService mThermalService;
 
     /** We lazily initialize it.*/
-    private PowerWhitelistManager mPowerWhitelistManager;
+    private PowerExemptionManager mPowerExemptionManager;
 
     private final ArrayMap<OnThermalStatusChangedListener, IThermalStatusListener>
             mListenerMap = new ArrayMap<>();
@@ -1063,12 +1064,12 @@
         mHandler = handler;
     }
 
-    private PowerWhitelistManager getPowerWhitelistManager() {
-        if (mPowerWhitelistManager == null) {
+    private PowerExemptionManager getPowerExemptionManager() {
+        if (mPowerExemptionManager == null) {
             // No need for synchronization; getSystemService() will return the same object anyway.
-            mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+            mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
         }
-        return mPowerWhitelistManager;
+        return mPowerExemptionManager;
     }
 
     /**
@@ -1339,12 +1340,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn off.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned on it will be turned off. If all displays are off as a result of this action the
-     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
      * default display group} is already off then nothing will happen.
      *
      * <p>If the device is an Android TV playback device and the current active source on the
@@ -1372,12 +1373,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn off.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned on it will be turned off. If all displays are off as a result of this action the
-     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * device will be put to sleep. If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP
      * default display group} is already off then nothing will happen.
      *
      * <p>
@@ -1409,12 +1410,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn on.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
-     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
      * on then nothing will happen.
      *
      * <p>
@@ -1440,12 +1441,12 @@
     }
 
     /**
-     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group}
      * to turn on.
      *
-     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is
      * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
-     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already
      * on then nothing will happen.
      *
      * <p>
@@ -2144,7 +2145,7 @@
      * features to the app. Guardrails for extreme cases may still be applied.
      */
     public boolean isIgnoringBatteryOptimizations(String packageName) {
-        return getPowerWhitelistManager().isWhitelisted(packageName, true);
+        return getPowerExemptionManager().isAllowListed(packageName, true);
     }
 
     /**
@@ -2270,8 +2271,8 @@
      * @param listener listener to be added,
      */
     public void addThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
-        this.addThermalStatusListener(mContext.getMainExecutor(), listener);
+        Objects.requireNonNull(listener, "listener cannot be null");
+        addThermalStatusListener(mContext.getMainExecutor(), listener);
     }
 
     /**
@@ -2282,8 +2283,8 @@
      */
     public void addThermalStatusListener(@NonNull @CallbackExecutor Executor executor,
             @NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
-        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
+        Objects.requireNonNull(executor, "executor cannot be null");
         Preconditions.checkArgument(!mListenerMap.containsKey(listener),
                 "Listener already registered: %s", listener);
         IThermalStatusListener internalListener = new IThermalStatusListener.Stub() {
@@ -2291,9 +2292,7 @@
             public void onStatusChange(int status) {
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    executor.execute(() -> {
-                        listener.onThermalStatusChanged(status);
-                    });
+                    executor.execute(() -> listener.onThermalStatusChanged(status));
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -2316,7 +2315,7 @@
      * @param listener listener to be removed
      */
     public void removeThermalStatusListener(@NonNull OnThermalStatusChangedListener listener) {
-        Preconditions.checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
         IThermalStatusListener internalListener = mListenerMap.get(listener);
         Preconditions.checkArgument(internalListener != null, "Listener was not added");
         try {
@@ -2945,6 +2944,7 @@
          *
          * @hide
          */
+        @SuppressLint("WakelockTimeout")
         public Runnable wrap(Runnable r) {
             acquire();
             return () -> {
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 0037761..0aafaf4 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,18 +18,25 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Range;
+import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Function;
 
 /**
  * Vibrator implementation that controls the main system vibrator.
@@ -51,7 +58,7 @@
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private AllVibratorsInfo mVibratorInfo;
+    private VibratorInfo mVibratorInfo;
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
@@ -71,6 +78,11 @@
                 return VibratorInfo.EMPTY_VIBRATOR_INFO;
             }
             int[] vibratorIds = mVibratorManager.getVibratorIds();
+            if (vibratorIds.length == 0) {
+                // It is known that the device has no vibrator, so cache and return info that
+                // reflects the lack of support for effects/primitives.
+                return mVibratorInfo = new NoVibratorInfo();
+            }
             VibratorInfo[] vibratorInfos = new VibratorInfo[vibratorIds.length];
             for (int i = 0; i < vibratorIds.length; i++) {
                 Vibrator vibrator = mVibratorManager.getVibrator(vibratorIds[i]);
@@ -83,7 +95,12 @@
                 }
                 vibratorInfos[i] = vibrator.getInfo();
             }
-            return mVibratorInfo = new AllVibratorsInfo(vibratorInfos);
+            if (vibratorInfos.length == 1) {
+                // Device has a single vibrator info, cache and return successfully loaded info.
+                return mVibratorInfo = new VibratorInfo(/* id= */ -1, vibratorInfos[0]);
+            }
+            // Device has multiple vibrators, generate a single info representing all of them.
+            return mVibratorInfo = new MultiVibratorInfo(vibratorInfos);
         }
     }
 
@@ -257,77 +274,282 @@
     }
 
     /**
-     * Represents all the vibrators information as a single {@link VibratorInfo}.
+     * Represents a device with no vibrator as a single {@link VibratorInfo}.
      *
-     * <p>This uses the first vibrator on the list as the default one for all hardware spec, but
-     * uses an intersection of all vibrators to decide the capabilities and effect/primitive
+     * @hide
+     */
+    @VisibleForTesting
+    public static class NoVibratorInfo extends VibratorInfo {
+        public NoVibratorInfo() {
+            // Use empty arrays to indicate no support, while null would indicate support unknown.
+            super(/* id= */ -1,
+                    /* capabilities= */ 0,
+                    /* supportedEffects= */ new SparseBooleanArray(),
+                    /* supportedBraking= */ new SparseBooleanArray(),
+                    /* supportedPrimitives= */ new SparseIntArray(),
+                    /* primitiveDelayMax= */ 0,
+                    /* compositionSizeMax= */ 0,
+                    /* pwlePrimitiveDurationMax= */ 0,
+                    /* pwleSizeMax= */ 0,
+                    /* qFactor= */ Float.NaN,
+                    new FrequencyProfile(/* resonantFrequencyHz= */ Float.NaN,
+                            /* minFrequencyHz= */ Float.NaN,
+                            /* frequencyResolutionHz= */ Float.NaN,
+                            /* maxAmplitudes= */ null));
+        }
+    }
+
+    /**
+     * Represents multiple vibrator information as a single {@link VibratorInfo}.
+     *
+     * <p>This uses an intersection of all vibrators to decide the capabilities and effect/primitive
      * support.
      *
      * @hide
      */
     @VisibleForTesting
-    public static class AllVibratorsInfo extends VibratorInfo {
-        private final VibratorInfo[] mVibratorInfos;
+    public static class MultiVibratorInfo extends VibratorInfo {
+        // Epsilon used for float comparison applied in calculations for the merged info.
+        private static final float EPSILON = 1e-5f;
 
-        public AllVibratorsInfo(VibratorInfo[] vibrators) {
-            super(/* id= */ -1, capabilitiesIntersection(vibrators),
-                    vibrators.length > 0 ? vibrators[0] : VibratorInfo.EMPTY_VIBRATOR_INFO);
-            mVibratorInfos = vibrators;
-        }
-
-        @Override
-        public int isEffectSupported(int effectId) {
-            if (mVibratorInfos.length == 0) {
-                return Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
-            }
-            int supported = Vibrator.VIBRATION_EFFECT_SUPPORT_YES;
-            for (VibratorInfo info : mVibratorInfos) {
-                int effectSupported = info.isEffectSupported(effectId);
-                if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
-                    return effectSupported;
-                } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
-                    supported = effectSupported;
-                }
-            }
-            return supported;
-        }
-
-        @Override
-        public boolean isPrimitiveSupported(int primitiveId) {
-            if (mVibratorInfos.length == 0) {
-                return false;
-            }
-            for (VibratorInfo info : mVibratorInfos) {
-                if (!info.isPrimitiveSupported(primitiveId)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public int getPrimitiveDuration(int primitiveId) {
-            int maxDuration = 0;
-            for (VibratorInfo info : mVibratorInfos) {
-                int duration = info.getPrimitiveDuration(primitiveId);
-                if (duration == 0) {
-                    return 0;
-                }
-                maxDuration = Math.max(maxDuration, duration);
-            }
-            return maxDuration;
+        public MultiVibratorInfo(VibratorInfo[] vibrators) {
+            super(/* id= */ -1,
+                    capabilitiesIntersection(vibrators),
+                    supportedEffectsIntersection(vibrators),
+                    supportedBrakingIntersection(vibrators),
+                    supportedPrimitivesAndDurationsIntersection(vibrators),
+                    integerLimitIntersection(vibrators, VibratorInfo::getPrimitiveDelayMax),
+                    integerLimitIntersection(vibrators, VibratorInfo::getCompositionSizeMax),
+                    integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
+                    integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
+                    floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
+                    frequencyProfileIntersection(vibrators));
         }
 
         private static int capabilitiesIntersection(VibratorInfo[] infos) {
-            if (infos.length == 0) {
-                return 0;
-            }
             int intersection = ~0;
             for (VibratorInfo info : infos) {
                 intersection &= info.getCapabilities();
             }
             return intersection;
         }
+
+        @Nullable
+        private static SparseBooleanArray supportedBrakingIntersection(VibratorInfo[] infos) {
+            for (VibratorInfo info : infos) {
+                if (!info.isBrakingSupportKnown()) {
+                    // If one vibrator support is unknown, then the intersection is also unknown.
+                    return null;
+                }
+            }
+
+            SparseBooleanArray intersection = new SparseBooleanArray();
+            SparseBooleanArray firstVibratorBraking = infos[0].getSupportedBraking();
+
+            brakingIdLoop:
+            for (int i = 0; i < firstVibratorBraking.size(); i++) {
+                int brakingId = firstVibratorBraking.keyAt(i);
+                if (!firstVibratorBraking.valueAt(i)) {
+                    // The first vibrator already doesn't support this braking, so skip it.
+                    continue brakingIdLoop;
+                }
+
+                for (int j = 1; j < infos.length; j++) {
+                    if (!infos[j].hasBrakingSupport(brakingId)) {
+                        // One vibrator doesn't support this braking, so the intersection doesn't.
+                        continue brakingIdLoop;
+                    }
+                }
+
+                intersection.put(brakingId, true);
+            }
+
+            return intersection;
+        }
+
+        @Nullable
+        private static SparseBooleanArray supportedEffectsIntersection(VibratorInfo[] infos) {
+            for (VibratorInfo info : infos) {
+                if (!info.isEffectSupportKnown()) {
+                    // If one vibrator support is unknown, then the intersection is also unknown.
+                    return null;
+                }
+            }
+
+            SparseBooleanArray intersection = new SparseBooleanArray();
+            SparseBooleanArray firstVibratorEffects = infos[0].getSupportedEffects();
+
+            effectIdLoop:
+            for (int i = 0; i < firstVibratorEffects.size(); i++) {
+                int effectId = firstVibratorEffects.keyAt(i);
+                if (!firstVibratorEffects.valueAt(i)) {
+                    // The first vibrator already doesn't support this effect, so skip it.
+                    continue effectIdLoop;
+                }
+
+                for (int j = 1; j < infos.length; j++) {
+                    if (infos[j].isEffectSupported(effectId) != VIBRATION_EFFECT_SUPPORT_YES) {
+                        // One vibrator doesn't support this effect, so the intersection doesn't.
+                        continue effectIdLoop;
+                    }
+                }
+
+                intersection.put(effectId, true);
+            }
+
+            return intersection;
+        }
+
+        @NonNull
+        private static SparseIntArray supportedPrimitivesAndDurationsIntersection(
+                VibratorInfo[] infos) {
+            SparseIntArray intersection = new SparseIntArray();
+            SparseIntArray firstVibratorPrimitives = infos[0].getSupportedPrimitives();
+
+            primitiveIdLoop:
+            for (int i = 0; i < firstVibratorPrimitives.size(); i++) {
+                int primitiveId = firstVibratorPrimitives.keyAt(i);
+                int primitiveDuration = firstVibratorPrimitives.valueAt(i);
+                if (primitiveDuration == 0) {
+                    // The first vibrator already doesn't support this primitive, so skip it.
+                    continue primitiveIdLoop;
+                }
+
+                for (int j = 1; j < infos.length; j++) {
+                    int vibratorPrimitiveDuration = infos[j].getPrimitiveDuration(primitiveId);
+                    if (vibratorPrimitiveDuration == 0) {
+                        // One vibrator doesn't support this primitive, so the intersection doesn't.
+                        continue primitiveIdLoop;
+                    } else {
+                        // The primitive vibration duration is the maximum among all vibrators.
+                        primitiveDuration = Math.max(primitiveDuration, vibratorPrimitiveDuration);
+                    }
+                }
+
+                intersection.put(primitiveId, primitiveDuration);
+            }
+            return intersection;
+        }
+
+        private static int integerLimitIntersection(VibratorInfo[] infos,
+                Function<VibratorInfo, Integer> propertyGetter) {
+            int limit = 0; // Limit 0 means unlimited
+            for (VibratorInfo info : infos) {
+                int vibratorLimit = propertyGetter.apply(info);
+                if ((limit == 0) || (vibratorLimit > 0 && vibratorLimit < limit)) {
+                    // This vibrator is limited and intersection is unlimited or has a larger limit:
+                    // use smaller limit here for the intersection.
+                    limit = vibratorLimit;
+                }
+            }
+            return limit;
+        }
+
+        private static float floatPropertyIntersection(VibratorInfo[] infos,
+                Function<VibratorInfo, Float> propertyGetter) {
+            float property = propertyGetter.apply(infos[0]);
+            if (Float.isNaN(property)) {
+                // If one vibrator is undefined then the intersection is undefined.
+                return Float.NaN;
+            }
+            for (int i = 1; i < infos.length; i++) {
+                if (Float.compare(property, propertyGetter.apply(infos[i])) != 0) {
+                    // If one vibrator has a different value then the intersection is undefined.
+                    return Float.NaN;
+                }
+            }
+            return property;
+        }
+
+        @NonNull
+        private static FrequencyProfile frequencyProfileIntersection(VibratorInfo[] infos) {
+            float freqResolution = floatPropertyIntersection(infos,
+                    info -> info.getFrequencyProfile().getFrequencyResolutionHz());
+            float resonantFreq = floatPropertyIntersection(infos,
+                    VibratorInfo::getResonantFrequencyHz);
+            Range<Float> freqRange = frequencyRangeIntersection(infos, freqResolution);
+
+            if ((freqRange == null) || Float.isNaN(freqResolution)) {
+                return new FrequencyProfile(resonantFreq, Float.NaN, freqResolution, null);
+            }
+
+            int amplitudeCount =
+                    Math.round(1 + (freqRange.getUpper() - freqRange.getLower()) / freqResolution);
+            float[] maxAmplitudes = new float[amplitudeCount];
+
+            // Use MAX_VALUE here to ensure that the FrequencyProfile constructor called with this
+            // will fail if the loop below is broken and do not replace filled values with actual
+            // vibrator measurements.
+            Arrays.fill(maxAmplitudes, Float.MAX_VALUE);
+
+            for (VibratorInfo info : infos) {
+                Range<Float> vibratorFreqRange = info.getFrequencyProfile().getFrequencyRangeHz();
+                float[] vibratorMaxAmplitudes = info.getFrequencyProfile().getMaxAmplitudes();
+                int vibratorStartIdx = Math.round(
+                        (freqRange.getLower() - vibratorFreqRange.getLower()) / freqResolution);
+                int vibratorEndIdx = vibratorStartIdx + maxAmplitudes.length - 1;
+
+                if ((vibratorStartIdx < 0) || (vibratorEndIdx >= vibratorMaxAmplitudes.length)) {
+                    Slog.w(TAG, "Error calculating the intersection of vibrator frequency"
+                            + " profiles: attempted to fetch from vibrator "
+                            + info.getId() + " max amplitude with bad index " + vibratorStartIdx);
+                    return new FrequencyProfile(resonantFreq, Float.NaN, Float.NaN, null);
+                }
+
+                for (int i = 0; i < maxAmplitudes.length; i++) {
+                    maxAmplitudes[i] = Math.min(maxAmplitudes[i],
+                            vibratorMaxAmplitudes[vibratorStartIdx + i]);
+                }
+            }
+
+            return new FrequencyProfile(resonantFreq, freqRange.getLower(),
+                    freqResolution, maxAmplitudes);
+        }
+
+        @Nullable
+        private static Range<Float> frequencyRangeIntersection(VibratorInfo[] infos,
+                float frequencyResolution) {
+            Range<Float> firstRange = infos[0].getFrequencyProfile().getFrequencyRangeHz();
+            if (firstRange == null) {
+                // If one vibrator is undefined then the intersection is undefined.
+                return null;
+            }
+            float intersectionLower = firstRange.getLower();
+            float intersectionUpper = firstRange.getUpper();
+
+            // Generate the intersection of all vibrator supported ranges, making sure that both
+            // min supported frequencies are aligned w.r.t. the frequency resolution.
+
+            for (int i = 1; i < infos.length; i++) {
+                Range<Float> vibratorRange = infos[i].getFrequencyProfile().getFrequencyRangeHz();
+                if (vibratorRange == null) {
+                    // If one vibrator is undefined then the intersection is undefined.
+                    return null;
+                }
+
+                if ((vibratorRange.getLower() >= intersectionUpper)
+                        || (vibratorRange.getUpper() <= intersectionLower)) {
+                    // If the range and intersection are disjoint then the intersection is undefined
+                    return null;
+                }
+
+                float frequencyDelta = Math.abs(intersectionLower - vibratorRange.getLower());
+                if ((frequencyDelta % frequencyResolution) > EPSILON) {
+                    // If the intersection is not aligned with one vibrator then it's undefined
+                    return null;
+                }
+
+                intersectionLower = Math.max(intersectionLower, vibratorRange.getLower());
+                intersectionUpper = Math.min(intersectionUpper, vibratorRange.getUpper());
+            }
+
+            if ((intersectionUpper - intersectionLower) < frequencyResolution) {
+                // If the intersection is empty then it's undefined.
+                return null;
+            }
+
+            return Range.create(intersectionLower, intersectionUpper);
+        }
     }
 
     /** Listener for all vibrators state change. */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f18c9c9..bd65a41 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4787,33 +4787,6 @@
     }
 
     /**
-     * Immediately removes the user or, if the user cannot be removed, such as when the user is
-     * the current user, then set the user as ephemeral so that it will be removed when it is
-     * stopped.
-     *
-     * @param evenWhenDisallowed when {@code true}, user is removed even if the caller has the
-     * {@link #DISALLOW_REMOVE_USER} or {@link #DISALLOW_REMOVE_MANAGED_PROFILE} restriction
-     *
-     * @return the {@link RemoveResult} code
-     *
-     * @deprecated  TODO(b/199446770): remove this call after converting all calls to
-     * removeUserWhenPossible(UserHandle, boolean)
-     *
-     * @hide
-     */
-    @Deprecated
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
-    public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
-            boolean evenWhenDisallowed) {
-        try {
-            return mService.removeUserWhenPossible(userId, evenWhenDisallowed);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Updates the user's name.
      *
      * @param userId the user's integer id
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index eba9ff1..23baa5d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -31,6 +31,7 @@
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
 import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorFrequencyProfile;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -208,8 +209,8 @@
     /**
      * Check whether the vibrator has independent frequency control.
      *
-     * @return True if the hardware can control the frequency of the vibrations, otherwise false.
-     * @hide
+     * @return True if the hardware can control the frequency of the vibrations independently of
+     * the vibration amplitude, false otherwise.
      */
     public boolean hasFrequencyControl() {
         // We currently can only control frequency of the vibration using the compose PWLE method.
@@ -229,28 +230,48 @@
     }
 
     /**
-     * Gets the resonant frequency of the vibrator.
+     * Gets the resonant frequency of the vibrator, if applicable.
      *
-     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     * this vibrator is a composite of multiple physical devices.
-     * @hide
+     * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+     * applicable, or if this vibrator is a composite of multiple physical devices with different
+     * frequencies.
      */
     public float getResonantFrequency() {
-        return getInfo().getResonantFrequency();
+        return getInfo().getResonantFrequencyHz();
     }
 
     /**
      * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
      *
-     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     *         this vibrator is a composite of multiple physical devices.
-     * @hide
+     * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown, not
+     * applicable, or if this vibrator is a composite of multiple physical devices with different
+     * Q factors.
      */
     public float getQFactor() {
         return getInfo().getQFactor();
     }
 
     /**
+     * Gets the profile that describes the vibrator output across the supported frequency range.
+     *
+     * <p>The profile describes the relative output acceleration that the device can reach when it
+     * vibrates at different frequencies.
+     *
+     * @return The frequency profile for this vibrator, or null if the vibrator does not have
+     * frequency control. If this vibrator is a composite of multiple physical devices then this
+     * will return a profile supported in all devices, or null if the intersection is empty or not
+     * available.
+     */
+    @Nullable
+    public VibratorFrequencyProfile getFrequencyProfile() {
+        VibratorInfo.FrequencyProfile frequencyProfile = getInfo().getFrequencyProfile();
+        if (frequencyProfile.isEmpty()) {
+            return null;
+        }
+        return new VibratorFrequencyProfile(frequencyProfile);
+    }
+
+    /**
      * Return the maximum amplitude the vibrator can play using the audio haptic channels.
      *
      * <p>This is a positive value, or {@link Float#NaN NaN} if it's unknown. If this returns a
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 5271c4d..00ce14f 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.vibrator.Braking;
@@ -26,6 +25,8 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -56,7 +57,7 @@
     private final int mPwlePrimitiveDurationMax;
     private final int mPwleSizeMax;
     private final float mQFactor;
-    private final FrequencyMapping mFrequencyMapping;
+    private final FrequencyProfile mFrequencyProfile;
 
     VibratorInfo(Parcel in) {
         mId = in.readInt();
@@ -69,7 +70,15 @@
         mPwlePrimitiveDurationMax = in.readInt();
         mPwleSizeMax = in.readInt();
         mQFactor = in.readFloat();
-        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
+        mFrequencyProfile = FrequencyProfile.CREATOR.createFromParcel(in);
+    }
+
+    public VibratorInfo(int id, @NonNull VibratorInfo baseVibratorInfo) {
+        this(id, baseVibratorInfo.mCapabilities, baseVibratorInfo.mSupportedEffects,
+                baseVibratorInfo.mSupportedBraking, baseVibratorInfo.mSupportedPrimitives,
+                baseVibratorInfo.mPrimitiveDelayMax, baseVibratorInfo.mCompositionSizeMax,
+                baseVibratorInfo.mPwlePrimitiveDurationMax, baseVibratorInfo.mPwleSizeMax,
+                baseVibratorInfo.mQFactor, baseVibratorInfo.mFrequencyProfile);
     }
 
     /**
@@ -92,7 +101,7 @@
      * @param pwleSizeMax              The maximum number of primitives supported by a PWLE
      *                                 composition.
      * @param qFactor                  The vibrator quality factor.
-     * @param frequencyMapping         The description of the vibrator supported frequencies and max
+     * @param frequencyProfile         The description of the vibrator supported frequencies and max
      *                                 amplitude mappings.
      * @hide
      */
@@ -100,7 +109,9 @@
             @Nullable SparseBooleanArray supportedBraking,
             @NonNull SparseIntArray supportedPrimitives, int primitiveDelayMax,
             int compositionSizeMax, int pwlePrimitiveDurationMax, int pwleSizeMax,
-            float qFactor, @NonNull FrequencyMapping frequencyMapping) {
+            float qFactor, @NonNull FrequencyProfile frequencyProfile) {
+        Preconditions.checkNotNull(supportedPrimitives);
+        Preconditions.checkNotNull(frequencyProfile);
         mId = id;
         mCapabilities = capabilities;
         mSupportedEffects = supportedEffects == null ? null : supportedEffects.clone();
@@ -111,14 +122,7 @@
         mPwlePrimitiveDurationMax = pwlePrimitiveDurationMax;
         mPwleSizeMax = pwleSizeMax;
         mQFactor = qFactor;
-        mFrequencyMapping = frequencyMapping;
-    }
-
-    protected VibratorInfo(int id, int capabilities, VibratorInfo baseVibrator) {
-        this(id, capabilities, baseVibrator.mSupportedEffects, baseVibrator.mSupportedBraking,
-                baseVibrator.mSupportedPrimitives, baseVibrator.mPrimitiveDelayMax,
-                baseVibrator.mCompositionSizeMax, baseVibrator.mPwlePrimitiveDurationMax,
-                baseVibrator.mPwleSizeMax, baseVibrator.mQFactor, baseVibrator.mFrequencyMapping);
+        mFrequencyProfile = frequencyProfile;
     }
 
     @Override
@@ -133,7 +137,7 @@
         dest.writeInt(mPwlePrimitiveDurationMax);
         dest.writeInt(mPwleSizeMax);
         dest.writeFloat(mQFactor);
-        dest.writeParcelable(mFrequencyMapping, flags);
+        mFrequencyProfile.writeToParcel(dest, flags);
     }
 
     @Override
@@ -170,13 +174,13 @@
                 && Objects.equals(mSupportedEffects, that.mSupportedEffects)
                 && Objects.equals(mSupportedBraking, that.mSupportedBraking)
                 && Objects.equals(mQFactor, that.mQFactor)
-                && Objects.equals(mFrequencyMapping, that.mFrequencyMapping);
+                && Objects.equals(mFrequencyProfile, that.mFrequencyProfile);
     }
 
     @Override
     public int hashCode() {
         int hashCode = Objects.hash(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
-                mQFactor, mFrequencyMapping);
+                mQFactor, mFrequencyProfile);
         for (int i = 0; i < mSupportedPrimitives.size(); i++) {
             hashCode = 31 * hashCode + mSupportedPrimitives.keyAt(i);
             hashCode = 31 * hashCode + mSupportedPrimitives.valueAt(i);
@@ -198,7 +202,7 @@
                 + ", mPwlePrimitiveDurationMax=" + mPwlePrimitiveDurationMax
                 + ", mPwleSizeMax=" + mPwleSizeMax
                 + ", mQFactor=" + mQFactor
-                + ", mFrequencyMapping=" + mFrequencyMapping
+                + ", mFrequencyProfile=" + mFrequencyProfile
                 + '}';
     }
 
@@ -234,6 +238,30 @@
         return Braking.NONE;
     }
 
+    /** @hide */
+    @Nullable
+    public SparseBooleanArray getSupportedBraking() {
+        if (mSupportedBraking == null) {
+            return null;
+        }
+        return mSupportedBraking.clone();
+    }
+
+    /** @hide */
+    public boolean isBrakingSupportKnown() {
+        return mSupportedBraking != null;
+    }
+
+    /** @hide */
+    public boolean hasBrakingSupport(@Braking int braking) {
+        return (mSupportedBraking != null) && mSupportedBraking.get(braking);
+    }
+
+    /** @hide */
+    public boolean isEffectSupportKnown() {
+        return mSupportedEffects != null;
+    }
+
     /**
      * Query whether the vibrator supports the given effect.
      *
@@ -252,6 +280,15 @@
                 : Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
     }
 
+    /** @hide */
+    @Nullable
+    public SparseBooleanArray getSupportedEffects() {
+        if (mSupportedEffects == null) {
+            return null;
+        }
+        return mSupportedEffects.clone();
+    }
+
     /**
      * Query whether the vibrator supports the given primitive.
      *
@@ -276,6 +313,11 @@
         return mSupportedPrimitives.get(primitiveId);
     }
 
+    /** @hide */
+    public SparseIntArray getSupportedPrimitives() {
+        return mSupportedPrimitives.clone();
+    }
+
     /**
      * Query the maximum delay supported for a primitive in a composed effect.
      *
@@ -329,8 +371,8 @@
      * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
      *         this vibrator is a composite of multiple physical devices.
      */
-    public float getResonantFrequency() {
-        return mFrequencyMapping.mResonantFrequencyHz;
+    public float getResonantFrequencyHz() {
+        return mFrequencyProfile.mResonantFrequencyHz;
     }
 
     /**
@@ -344,31 +386,14 @@
     }
 
     /**
-     * Return a range of frequency values supported by the vibrator.
+     * Gets the profile of supported frequencies, including the measurements of maximum relative
+     * output acceleration for supported vibration frequencies.
      *
-     * @return A range of frequency values supported, in hertz. The range will always contain the
-     * device resonant frequency. Devices without frequency control will return null.
-     * @hide
+     * <p>If the devices does not have frequency control then the profile should be empty.
      */
-    @Nullable
-    public Range<Float> getFrequencyRangeHz() {
-        return mFrequencyMapping.mFrequencyRangeHz;
-    }
-
-    /**
-     * Return the maximum amplitude the vibrator can play at given frequency.
-     *
-     * @param frequencyHz The frequency, in hertz, for query.
-
-     * @return a value in [0,1] representing the maximum amplitude the device can play at given
-     * frequency. Devices without frequency control will return 0 to any input. Devices with
-     * frequency control will return the supported value, for input in
-     * {@link #getFrequencyRangeHz()}, and 0 for any other input.
-     * @hide
-     */
-    @FloatRange(from = 0, to = 1)
-    public float getMaxAmplitude(float frequencyHz) {
-        return mFrequencyMapping.getMaxAmplitude(frequencyHz);
+    @NonNull
+    public FrequencyProfile getFrequencyProfile() {
+        return mFrequencyProfile;
     }
 
     protected long getCapabilities() {
@@ -452,7 +477,7 @@
      * Describes the maximum relative output acceleration that can be achieved for each supported
      * frequency in a specific vibrator.
      *
-     * <p>This mapping is defined by the following parameters:
+     * <p>This profile is defined by the following parameters:
      *
      * <ol>
      *     <li>{@code minFrequencyHz}, {@code resonantFrequencyHz} and {@code frequencyResolutionHz}
@@ -466,7 +491,7 @@
      *
      * @hide
      */
-    public static final class FrequencyMapping implements Parcelable {
+    public static final class FrequencyProfile implements Parcelable {
         @Nullable
         private final Range<Float> mFrequencyRangeHz;
         private final float mMinFrequencyHz;
@@ -474,7 +499,7 @@
         private final float mFrequencyResolutionHz;
         private final float[] mMaxAmplitudes;
 
-        FrequencyMapping(Parcel in) {
+        FrequencyProfile(Parcel in) {
             this(in.readFloat(), in.readFloat(), in.readFloat(), in.createFloatArray());
         }
 
@@ -484,13 +509,13 @@
          * @param resonantFrequencyHz   The vibrator resonant frequency, in hertz.
          * @param minFrequencyHz        Minimum supported frequency, in hertz.
          * @param frequencyResolutionHz The frequency resolution, in hertz, used by the max
-         *                              amplitudes mapping.
+         *                              amplitude measurements.
          * @param maxAmplitudes         The max amplitude supported by each supported frequency,
          *                              starting at minimum frequency with jumps of frequency
          *                              resolution.
          * @hide
          */
-        public FrequencyMapping(float resonantFrequencyHz, float minFrequencyHz,
+        public FrequencyProfile(float resonantFrequencyHz, float minFrequencyHz,
                 float frequencyResolutionHz, float[] maxAmplitudes) {
             mMinFrequencyHz = minFrequencyHz;
             mResonantFrequencyHz = resonantFrequencyHz;
@@ -500,18 +525,25 @@
                 System.arraycopy(maxAmplitudes, 0, mMaxAmplitudes, 0, maxAmplitudes.length);
             }
 
-            // If any required field is undefined then leave this mapping empty.
+            // If any required field is undefined or has a bad value then this profile is invalid.
             boolean isValid = !Float.isNaN(resonantFrequencyHz)
+                    && (resonantFrequencyHz > 0)
                     && !Float.isNaN(minFrequencyHz)
+                    && (minFrequencyHz > 0)
                     && !Float.isNaN(frequencyResolutionHz)
+                    && (frequencyResolutionHz > 0)
                     && (mMaxAmplitudes.length > 0);
 
+            // If any max amplitude is outside the allowed range then this profile is invalid.
+            for (int i = 0; i < mMaxAmplitudes.length; i++) {
+                isValid &= (mMaxAmplitudes[i] >= 0) && (mMaxAmplitudes[i] <= 1);
+            }
+
             float maxFrequencyHz = isValid
                     ? minFrequencyHz + frequencyResolutionHz * (mMaxAmplitudes.length - 1)
                     : Float.NaN;
 
-            // If the non-empty mapping does not have min < resonant < max frequency respected
-            // then leave this mapping empty.
+            // If the constraint min < resonant < max is not met then it is invalid.
             isValid &= !Float.isNaN(maxFrequencyHz)
                     && (resonantFrequencyHz >= minFrequencyHz)
                     && (resonantFrequencyHz <= maxFrequencyHz)
@@ -520,14 +552,17 @@
             mFrequencyRangeHz = isValid ? Range.create(minFrequencyHz, maxFrequencyHz) : null;
         }
 
-        /**
-         * Returns true if this frequency mapping is empty, i.e. the only supported is the resonant
-         * frequency.
-         */
+        /** Returns true if the supported frequency range is empty. */
         public boolean isEmpty() {
             return mFrequencyRangeHz == null;
         }
 
+        /** Returns the supported frequency range, in hertz. */
+        @Nullable
+        public Range<Float> getFrequencyRangeHz() {
+            return mFrequencyRangeHz;
+        }
+
         /**
          * Returns the maximum relative amplitude the vibrator can reach while playing at the
          * given frequency.
@@ -535,24 +570,43 @@
          * @param frequencyHz frequency, in hertz, for query.
          * @return A value in [0,1] representing the max relative amplitude supported at the given
          * frequency. This will return 0 if the frequency is outside the supported range, or if the
-         * mapping is empty.
+         * supported frequency range is empty.
          */
         public float getMaxAmplitude(float frequencyHz) {
-            if (isEmpty() || Float.isNaN(frequencyHz)) {
+            if (isEmpty() || Float.isNaN(frequencyHz) || !mFrequencyRangeHz.contains(frequencyHz)) {
                 // Unsupported frequency requested, vibrator cannot play at this frequency.
                 return 0;
             }
-            float position = (frequencyHz - mMinFrequencyHz) / mFrequencyResolutionHz;
-            int floorIndex = (int) Math.floor(position);
-            int ceilIndex = (int) Math.ceil(position);
-            if (floorIndex < 0 || floorIndex >= mMaxAmplitudes.length) {
-                return 0;
-            }
-            if (floorIndex != ceilIndex && ceilIndex < mMaxAmplitudes.length) {
-                // Value in between two mapped frequency values, use the lowest supported one.
-                return MathUtils.min(mMaxAmplitudes[floorIndex], mMaxAmplitudes[ceilIndex]);
-            }
-            return mMaxAmplitudes[floorIndex];
+
+            // Subtract minFrequencyHz to simplify offset calculations.
+            float mappingFreq = frequencyHz - mMinFrequencyHz;
+
+            // Find the bucket to interpolate within.
+            // Any calculated index should be safe, except exactly equal to max amplitude can be
+            // one step too high, so constrain it to guarantee safety.
+            int startIdx = MathUtils.constrain(
+                    /* amount= */ (int) Math.floor(mappingFreq / mFrequencyResolutionHz),
+                    /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+            int nextIdx = MathUtils.constrain(
+                    /* amount= */ startIdx + 1,
+                    /* low= */ 0, /* high= */ mMaxAmplitudes.length - 1);
+
+            // Linearly interpolate the amplitudes based on the frequency range of the bucket.
+            return MathUtils.constrainedMap(
+                    mMaxAmplitudes[startIdx], mMaxAmplitudes[nextIdx],
+                    startIdx * mFrequencyResolutionHz, nextIdx * mFrequencyResolutionHz,
+                    mappingFreq);
+        }
+
+        /** Returns the raw list of maximum relative output accelerations from the vibrator. */
+        @NonNull
+        public float[] getMaxAmplitudes() {
+            return Arrays.copyOf(mMaxAmplitudes, mMaxAmplitudes.length);
+        }
+
+        /** Returns the raw frequency resolution used for max amplitude measurements, in hertz. */
+        public float getFrequencyResolutionHz() {
+            return mFrequencyResolutionHz;
         }
 
         @Override
@@ -573,10 +627,10 @@
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof FrequencyMapping)) {
+            if (!(o instanceof FrequencyProfile)) {
                 return false;
             }
-            FrequencyMapping that = (FrequencyMapping) o;
+            FrequencyProfile that = (FrequencyProfile) o;
             return Float.compare(mMinFrequencyHz, that.mMinFrequencyHz) == 0
                     && Float.compare(mResonantFrequencyHz, that.mResonantFrequencyHz) == 0
                     && Float.compare(mFrequencyResolutionHz, that.mFrequencyResolutionHz) == 0
@@ -593,7 +647,7 @@
 
         @Override
         public String toString() {
-            return "FrequencyMapping{"
+            return "FrequencyProfile{"
                     + "mFrequencyRange=" + mFrequencyRangeHz
                     + ", mMinFrequency=" + mMinFrequencyHz
                     + ", mResonantFrequency=" + mResonantFrequencyHz
@@ -603,16 +657,16 @@
         }
 
         @NonNull
-        public static final Creator<FrequencyMapping> CREATOR =
-                new Creator<FrequencyMapping>() {
+        public static final Creator<FrequencyProfile> CREATOR =
+                new Creator<FrequencyProfile>() {
                     @Override
-                    public FrequencyMapping createFromParcel(Parcel in) {
-                        return new FrequencyMapping(in);
+                    public FrequencyProfile createFromParcel(Parcel in) {
+                        return new FrequencyProfile(in);
                     }
 
                     @Override
-                    public FrequencyMapping[] newArray(int size) {
-                        return new FrequencyMapping[size];
+                    public FrequencyProfile[] newArray(int size) {
+                        return new FrequencyProfile[size];
                     }
                 };
     }
@@ -629,8 +683,8 @@
         private int mPwlePrimitiveDurationMax;
         private int mPwleSizeMax;
         private float mQFactor = Float.NaN;
-        private FrequencyMapping mFrequencyMapping =
-                new FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
+        private FrequencyProfile mFrequencyProfile =
+                new FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
 
         /** A builder class for a {@link VibratorInfo}. */
         public Builder(int id) {
@@ -702,8 +756,8 @@
 
         /** Configure the vibrator frequency information like resonant frequency and bandwidth. */
         @NonNull
-        public Builder setFrequencyMapping(FrequencyMapping frequencyMapping) {
-            mFrequencyMapping = frequencyMapping;
+        public Builder setFrequencyProfile(@NonNull FrequencyProfile frequencyProfile) {
+            mFrequencyProfile = frequencyProfile;
             return this;
         }
 
@@ -712,7 +766,7 @@
         public VibratorInfo build() {
             return new VibratorInfo(mId, mCapabilities, mSupportedEffects, mSupportedBraking,
                     mSupportedPrimitives, mPrimitiveDelayMax, mCompositionSizeMax,
-                    mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyMapping);
+                    mPwlePrimitiveDurationMax, mPwleSizeMax, mQFactor, mFrequencyProfile);
         }
 
         /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/os/logcat/ILogcatManagerService.aidl
index 2b3e961..68b5679 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.os.logcat;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * @hide
+ */
+interface ILogcatManagerService {
+    void startThread(in int uid, in int gid, in int pid, in int fd);
+    void finishThread(in int uid, in int gid, in int pid, in int fd);
+}
 
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
diff --git a/core/java/android/os/logcat/OWNERS b/core/java/android/os/logcat/OWNERS
new file mode 100644
index 0000000..cb21a6f
--- /dev/null
+++ b/core/java/android/os/logcat/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/logcat/OWNERS
diff --git a/core/java/android/os/vibrator/VibratorFrequencyProfile.java b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
new file mode 100644
index 0000000..23b45ae
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorFrequencyProfile.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.os.VibratorInfo;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Describes the output of a {@link android.os.Vibrator} for different vibration frequencies.
+ *
+ * <p>The profile contains the minimum and maximum supported vibration frequencies, if the device
+ * supports independent frequency control.
+ *
+ * <p>It also describes the relative output acceleration of a vibration at different supported
+ * frequencies. The acceleration is defined by a relative amplitude value between 0 and 1,
+ * inclusive, where 0 represents the vibrator off state and 1 represents the maximum output
+ * acceleration that the vibrator can reach across all supported frequencies.
+ *
+ * <p>The measurements are returned as an array of uniformly distributed amplitude values for
+ * frequencies between the minimum and maximum supported ones. The measurement interval is the
+ * frequency increment between each pair of amplitude values.
+ *
+ * <p>Vibrators without independent frequency control do not have a frequency profile.
+ */
+public final class VibratorFrequencyProfile {
+
+    private final VibratorInfo.FrequencyProfile mFrequencyProfile;
+
+    /** @hide */
+    public VibratorFrequencyProfile(@NonNull VibratorInfo.FrequencyProfile frequencyProfile) {
+        Preconditions.checkArgument(!frequencyProfile.isEmpty(),
+                "Frequency profile must have a non-empty frequency range");
+        mFrequencyProfile = frequencyProfile;
+    }
+
+    /**
+     * Measurements of the maximum relative amplitude the vibrator can achieve for each supported
+     * frequency.
+     *
+     * <p>The frequency of a measurement is determined as:
+     *
+     * {@code getMinFrequency() + measurementIndex * getMaxAmplitudeMeasurementInterval()}
+     *
+     * <p>The returned list will not be empty, and will have entries representing frequencies from
+     * {@link #getMinFrequency()} to {@link #getMaxFrequency()}, inclusive.
+     *
+     * @return Array of maximum relative amplitude measurements, each value is between 0 and 1,
+     * inclusive.
+     */
+    @NonNull
+    @FloatRange(from = 0, to = 1)
+    public float[] getMaxAmplitudeMeasurements() {
+        // VibratorInfo getters always return a copy or clone of the data objects.
+        return mFrequencyProfile.getMaxAmplitudes();
+    }
+
+    /**
+     * Gets the frequency interval used to measure the maximum relative amplitudes.
+     *
+     * @return the frequency interval used for the measurement, in hertz.
+     */
+    public float getMaxAmplitudeMeasurementInterval() {
+        return mFrequencyProfile.getFrequencyResolutionHz();
+    }
+
+    /**
+     * Gets the minimum frequency supported by the vibrator.
+     *
+     * @return the minimum frequency supported by the vibrator, in hertz.
+     */
+    public float getMinFrequency() {
+        return mFrequencyProfile.getFrequencyRangeHz().getLower();
+    }
+
+    /**
+     * Gets the maximum frequency supported by the vibrator.
+     *
+     * @return the maximum frequency supported by the vibrator, in hertz.
+     */
+    public float getMaxFrequency() {
+        return mFrequencyProfile.getFrequencyRangeHz().getUpper();
+    }
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 0894e37..c9dd06c 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -56,6 +56,9 @@
             in AndroidFuture<String> callback);
     void getUnusedAppCount(
             in AndroidFuture callback);
+    void getHibernationEligibility(
+                in String packageName,
+                in AndroidFuture callback);
     void revokeOwnPermissionsOnKill(in String packageName, in List<String> permissions,
             in AndroidFuture callback);
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index a0115a9..0cf06aa 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -128,6 +128,51 @@
     /** Count and app even if it is a system app. */
     public static final int COUNT_WHEN_SYSTEM = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "HIBERNATION_ELIGIBILITY_"}, value = {
+            HIBERNATION_ELIGIBILITY_UNKNOWN,
+            HIBERNATION_ELIGIBILITY_ELIGIBLE,
+            HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM,
+            HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HibernationEligibilityFlag {}
+
+    /**
+     * Unknown whether package is eligible for hibernation.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1;
+
+    /**
+     * Package is eligible for app hibernation and may be hibernated when the job runs.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0;
+
+    /**
+     * Package is not eligible for app hibernation because it is categorically exempt via the
+     * system.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1;
+
+    /**
+     * Package is not eligible for app hibernation because it has been exempt by the user's
+     * preferences. Note that this should not be set if the package is exempt from hibernation by
+     * the system as the user preference would have no effect.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2;
+
     /**
      * Callback for delivering the result of {@link #revokeRuntimePermissions}.
      */
@@ -819,6 +864,39 @@
     }
 
     /**
+     * Get the hibernation eligibility of a package. See {@link HibernationEligibilityFlag}.
+     *
+     * @param packageName package name to check eligibility
+     * @param executor executor to run callback on
+     * @param callback callback for when result is generated
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION)
+    public void getHibernationEligibility(@NonNull String packageName,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer callback) {
+        checkNotNull(executor);
+        checkNotNull(callback);
+
+        mRemoteService.postAsync(service -> {
+            AndroidFuture<Integer> eligibilityResult = new AndroidFuture<>();
+            service.getHibernationEligibility(packageName, eligibilityResult);
+            return eligibilityResult;
+        }).whenCompleteAsync((eligibility, err) -> {
+            if (err != null) {
+                Log.e(TAG, "Error getting hibernation eligibility", err);
+                callback.accept(HIBERNATION_ELIGIBILITY_UNKNOWN);
+            } else {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    callback.accept(eligibility);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }, executor);
+    }
+
+    /**
      * Triggers the revocation of one or more permissions for a package, under the following
      * conditions:
      * <ul>
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index b1e3cfc..8d9f82b 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -375,6 +375,22 @@
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
 
+    /**
+     * Get the hibernation eligibility of the app. See
+     * {@link android.permission.PermissionControllerManager.HibernationEligibilityFlag}.
+     *
+     * @param packageName package to check eligibility
+     * @param callback callback after eligibility is returned
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION)
+    public void onGetHibernationEligibility(@NonNull String packageName,
+            @NonNull IntConsumer callback) {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
     @Override
     public final @NonNull IBinder onBind(Intent intent) {
         return new IPermissionController.Stub() {
@@ -669,6 +685,22 @@
             }
 
             @Override
+            public void getHibernationEligibility(@NonNull String packageName,
+                    @NonNull AndroidFuture callback) {
+                try {
+                    Objects.requireNonNull(callback);
+
+                    enforceSomePermissionsGrantedToCaller(
+                            Manifest.permission.MANAGE_APP_HIBERNATION);
+
+                    PermissionControllerService.this.onGetHibernationEligibility(packageName,
+                            callback::complete);
+                } catch (Throwable t) {
+                    callback.completeExceptionally(t);
+                }
+            }
+
+            @Override
             public void revokeOwnPermissionsOnKill(@NonNull String packageName,
                     @NonNull List<String> permissions, @NonNull AndroidFuture callback) {
                 try {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e4aee76..15f13eb 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -33,6 +33,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
@@ -239,6 +240,41 @@
     }
 
     /**
+     *
+     * Similar to checkPermissionForDataDelivery, except it results in an app op start, rather than
+     * a note. If this method is used, then {@link #finishDataDelivery(String, AttributionSource)}
+     * must be used when access is finished.
+     *
+     * @param permission The permission to check.
+     * @param attributionSource the permission identity
+     * @param message A message describing the reason the permission was checked
+     * @return The permission check result which is either {@link #PERMISSION_GRANTED}
+     *     or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
+     *
+     * @see #checkPermissionForDataDelivery(String, AttributionSource, String)
+     */
+    @PermissionCheckerManager.PermissionResult
+    public int checkPermissionForStartDataDelivery(@NonNull String permission,
+            @NonNull AttributionSource attributionSource, @Nullable String message) {
+        return PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
+                // FIXME(b/199526514): PID should be passed inside AttributionSource.
+                PermissionChecker.PID_UNKNOWN, attributionSource, message, true);
+    }
+
+    /**
+     * Indicate that usage has finished for an {@link AttributionSource} started with
+     * {@link #checkPermissionForStartDataDelivery(String, AttributionSource, String)}
+     *
+     * @param permission The permission to check.
+     * @param attributionSource the permission identity to finish
+     */
+    public void finishDataDelivery(@NonNull String permission,
+            @NonNull AttributionSource attributionSource) {
+        PermissionChecker.finishDataDelivery(mContext, AppOpsManager.permissionToOp(permission),
+                attributionSource);
+    }
+
+    /**
      * Checks whether a given data access chain described by the given {@link AttributionSource}
      * has a given permission. Call this method if you are the datasource which would not blame you
      * for access to the data since you are the data. Use this API if you are the datasource of the
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 133e384..bb1f393 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.service.dreams;
 
 import android.annotation.IdRes;
@@ -57,7 +58,6 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.DumpUtils.Dump;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -111,7 +111,7 @@
  * <p>If specified with the {@code <meta-data>} element,
  * additional information for the dream is defined using the
  * {@link android.R.styleable#Dream &lt;dream&gt;} element in a separate XML file.
- * Currently, the only addtional
+ * Currently, the only additional
  * information you can provide is for a settings activity that allows the user to configure
  * the dream behavior. For example:</p>
  * <p class="code-caption">res/xml/my_dream.xml</p>
@@ -159,7 +159,8 @@
  * </pre>
  */
 public class DreamService extends Service implements Window.Callback {
-    private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
+    private final String mTag =
+            DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
 
     /**
      * The name of the dream manager service.
@@ -224,13 +225,13 @@
     private DreamServiceWrapper mDreamServiceWrapper;
     private Runnable mDispatchAfterOnAttachedToWindow;
 
-    private OverlayConnection mOverlayConnection;
+    private final OverlayConnection mOverlayConnection;
 
     private static class OverlayConnection implements ServiceConnection {
         // Overlay set during onBind.
         private IDreamOverlay mOverlay;
         // A Queue of pending requests to execute on the overlay.
-        private ArrayDeque<Consumer<IDreamOverlay>> mRequests;
+        private final ArrayDeque<Consumer<IDreamOverlay>> mRequests;
 
         private boolean mBound;
 
@@ -292,7 +293,7 @@
         }
     }
 
-    private IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
+    private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
         @Override
         public void onExitRequested() {
             // Simply finish dream when exit is requested.
@@ -319,11 +320,11 @@
     public boolean dispatchKeyEvent(KeyEvent event) {
         // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
         if (!mInteractive) {
-            if (mDebug) Slog.v(TAG, "Waking up on keyEvent");
+            if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
             wakeUp();
             return true;
         } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            if (mDebug) Slog.v(TAG, "Waking up on back key");
+            if (mDebug) Slog.v(mTag, "Waking up on back key");
             wakeUp();
             return true;
         }
@@ -334,7 +335,7 @@
     @Override
     public boolean dispatchKeyShortcutEvent(KeyEvent event) {
         if (!mInteractive) {
-            if (mDebug) Slog.v(TAG, "Waking up on keyShortcutEvent");
+            if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent");
             wakeUp();
             return true;
         }
@@ -347,7 +348,7 @@
         // TODO: create more flexible version of mInteractive that allows clicks
         // but finish()es on any other kind of activity
         if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) {
-            if (mDebug) Slog.v(TAG, "Waking up on touchEvent");
+            if (mDebug) Slog.v(mTag, "Waking up on touchEvent");
             wakeUp();
             return true;
         }
@@ -358,7 +359,7 @@
     @Override
     public boolean dispatchTrackballEvent(MotionEvent event) {
         if (!mInteractive) {
-            if (mDebug) Slog.v(TAG, "Waking up on trackballEvent");
+            if (mDebug) Slog.v(mTag, "Waking up on trackballEvent");
             wakeUp();
             return true;
         }
@@ -369,7 +370,7 @@
     @Override
     public boolean dispatchGenericMotionEvent(MotionEvent event) {
         if (!mInteractive) {
-            if (mDebug) Slog.v(TAG, "Waking up on genericMotionEvent");
+            if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent");
             wakeUp();
             return true;
         }
@@ -624,7 +625,7 @@
     }
 
     /**
-     * Returns whether or not this dream is interactive.  Defaults to false.
+     * Returns whether this dream is interactive. Defaults to false.
      *
      * @see #setInteractive(boolean)
      */
@@ -648,7 +649,7 @@
     }
 
     /**
-     * Returns whether or not this dream is in fullscreen mode. Defaults to false.
+     * Returns whether this dream is in fullscreen mode. Defaults to false.
      *
      * @see #setFullscreen(boolean)
      */
@@ -670,7 +671,7 @@
     }
 
     /**
-     * Returns whether or not this dream keeps the screen bright while dreaming.
+     * Returns whether this dream keeps the screen bright while dreaming.
      * Defaults to false, allowing the screen to dim if necessary.
      *
      * @see #setScreenBright(boolean)
@@ -680,7 +681,7 @@
     }
 
     /**
-     * Marks this dream as windowless.  Only available to doze dreams.
+     * Marks this dream as windowless. Only available to doze dreams.
      *
      * @hide
      *
@@ -690,7 +691,7 @@
     }
 
     /**
-     * Returns whether or not this dream is windowless.  Only available to doze dreams.
+     * Returns whether this dream is windowless. Only available to doze dreams.
      *
      * @hide
      */
@@ -717,12 +718,12 @@
      * Starts dozing, entering a deep dreamy sleep.
      * <p>
      * Dozing enables the system to conserve power while the user is not actively interacting
-     * with the device.  While dozing, the display will remain on in a low-power state
+     * with the device. While dozing, the display will remain on in a low-power state
      * and will continue to show its previous contents but the application processor and
      * other system components will be allowed to suspend when possible.
      * </p><p>
      * While the application processor is suspended, the dream may stop executing code
-     * for long periods of time.  Prior to being suspended, the dream may schedule periodic
+     * for long periods of time. Prior to being suspended, the dream may schedule periodic
      * wake-ups to render new content by scheduling an alarm with the {@link AlarmManager}.
      * The dream may also keep the CPU awake by acquiring a
      * {@link android.os.PowerManager#PARTIAL_WAKE_LOCK partial wake lock} when necessary.
@@ -731,7 +732,7 @@
      * awake for very long.
      * </p><p>
      * It is a good idea to call this method some time after the dream's entry animation
-     * has completed and the dream is ready to doze.  It is important to completely
+     * has completed and the dream is ready to doze. It is important to completely
      * finish all of the work needed before dozing since the application processor may
      * be suspended at any moment once this method is called unless other wake locks
      * are being held.
@@ -752,7 +753,7 @@
 
     private void updateDoze() {
         if (mDreamToken == null) {
-            Slog.w(TAG, "Updating doze without a dream token.");
+            Slog.w(mTag, "Updating doze without a dream token.");
             return;
         }
 
@@ -768,7 +769,7 @@
     /**
      * Stops dozing, returns to active dreaming.
      * <p>
-     * This method reverses the effect of {@link #startDozing}.  From this moment onward,
+     * This method reverses the effect of {@link #startDozing}. From this moment onward,
      * the application processor will be kept awake as long as the dream is running
      * or until the dream starts dozing again.
      * </p>
@@ -790,12 +791,11 @@
 
     /**
      * Returns true if the dream will allow the system to enter a low-power state while
-     * it is running without actually turning off the screen.  Defaults to false,
+     * it is running without actually turning off the screen. Defaults to false,
      * keeping the application processor awake while the dream is running.
      *
      * @return True if the dream is dozing.
      *
-     * @see #setDozing(boolean)
      * @hide For use by system UI components only.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -822,7 +822,7 @@
      * Sets the screen state to use while dozing.
      * <p>
      * The value of this property determines the power state of the primary display
-     * once {@link #startDozing} has been called.  The default value is
+     * once {@link #startDozing} has been called. The default value is
      * {@link Display#STATE_UNKNOWN} which lets the system decide.
      * The dream may set a different state before starting to doze and may
      * perform transitions between states while dozing to conserve power and
@@ -836,7 +836,7 @@
      * If not using Sidekick, it is recommended that the state be set to
      * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
      * finished drawing and before it releases its wakelock
-     * to allow the display hardware to be fully suspended.  While suspended,
+     * to allow the display hardware to be fully suspended. While suspended,
      * the display will preserve its on-screen contents.
      * </p><p>
      * If the doze suspend state is used, the dream must make sure to set the mode back
@@ -844,7 +844,7 @@
      * since the display updates may be ignored and not seen by the user otherwise.
      * </p><p>
      * The set of available display power states and their behavior while dozing is
-     * hardware dependent and may vary across devices.  The dream may therefore
+     * hardware dependent and may vary across devices. The dream may therefore
      * need to be modified or configured to correctly support the hardware.
      * </p>
      *
@@ -883,19 +883,19 @@
      * Sets the screen brightness to use while dozing.
      * <p>
      * The value of this property determines the power state of the primary display
-     * once {@link #startDozing} has been called.  The default value is
+     * once {@link #startDozing} has been called. The default value is
      * {@link PowerManager#BRIGHTNESS_DEFAULT} which lets the system decide.
      * The dream may set a different brightness before starting to doze and may adjust
      * the brightness while dozing to conserve power and achieve various effects.
      * </p><p>
      * Note that dream may specify any brightness in the full 0-255 range, including
      * values that are less than the minimum value for manual screen brightness
-     * adjustments by the user.  In particular, the value may be set to 0 which may
+     * adjustments by the user. In particular, the value may be set to 0 which may
      * turn off the backlight entirely while still leaving the screen on although
      * this behavior is device dependent and not guaranteed.
      * </p><p>
      * The available range of display brightness values and their behavior while dozing is
-     * hardware dependent and may vary across devices.  The dream may therefore
+     * hardware dependent and may vary across devices. The dream may therefore
      * need to be modified or configured to correctly support the hardware.
      * </p>
      *
@@ -922,7 +922,7 @@
      */
     @Override
     public void onCreate() {
-        if (mDebug) Slog.v(TAG, "onCreate()");
+        if (mDebug) Slog.v(mTag, "onCreate()");
         super.onCreate();
     }
 
@@ -930,7 +930,7 @@
      * Called when the dream's window has been created and is visible and animation may now begin.
      */
     public void onDreamingStarted() {
-        if (mDebug) Slog.v(TAG, "onDreamingStarted()");
+        if (mDebug) Slog.v(mTag, "onDreamingStarted()");
         // hook for subclasses
     }
 
@@ -939,7 +939,7 @@
      * before the window has been removed.
      */
     public void onDreamingStopped() {
-        if (mDebug) Slog.v(TAG, "onDreamingStopped()");
+        if (mDebug) Slog.v(mTag, "onDreamingStopped()");
         // hook for subclasses
     }
 
@@ -947,11 +947,11 @@
      * Called when the dream is being asked to stop itself and wake.
      * <p>
      * The default implementation simply calls {@link #finish} which ends the dream
-     * immediately.  Subclasses may override this function to perform a smooth exit
+     * immediately. Subclasses may override this function to perform a smooth exit
      * transition then call {@link #finish} afterwards.
      * </p><p>
      * Note that the dream will only be given a short period of time (currently about
-     * five seconds) to wake up.  If the dream does not finish itself in a timely manner
+     * five seconds) to wake up. If the dream does not finish itself in a timely manner
      * then the system will forcibly finish it once the time allowance is up.
      * </p>
      */
@@ -962,7 +962,7 @@
     /** {@inheritDoc} */
     @Override
     public final IBinder onBind(Intent intent) {
-        if (mDebug) Slog.v(TAG, "onBind() intent = " + intent);
+        if (mDebug) Slog.v(mTag, "onBind() intent = " + intent);
         mDreamServiceWrapper = new DreamServiceWrapper();
 
         // Connect to the overlay service if present.
@@ -981,7 +981,7 @@
      * </p>
      */
     public final void finish() {
-        if (mDebug) Slog.v(TAG, "finish(): mFinished=" + mFinished);
+        if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
 
         Activity activity = mActivity;
         if (activity != null) {
@@ -998,7 +998,7 @@
         mFinished = true;
 
         if (mDreamToken == null) {
-            Slog.w(TAG, "Finish was called before the dream was attached.");
+            Slog.w(mTag, "Finish was called before the dream was attached.");
             stopSelf();
             return;
         }
@@ -1026,8 +1026,10 @@
     }
 
     private void wakeUp(boolean fromSystem) {
-        if (mDebug) Slog.v(TAG, "wakeUp(): fromSystem=" + fromSystem
-                + ", mWaking=" + mWaking + ", mFinished=" + mFinished);
+        if (mDebug) {
+            Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking
+                    + ", mFinished=" + mFinished);
+        }
 
         if (!mWaking && !mFinished) {
             mWaking = true;
@@ -1052,7 +1054,7 @@
             // it we were finishing immediately.
             if (!fromSystem && !mFinished) {
                 if (mActivity == null) {
-                    Slog.w(TAG, "WakeUp was called before the dream was attached.");
+                    Slog.w(mTag, "WakeUp was called before the dream was attached.");
                 } else {
                     try {
                         mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
@@ -1067,7 +1069,7 @@
     /** {@inheritDoc} */
     @Override
     public void onDestroy() {
-        if (mDebug) Slog.v(TAG, "onDestroy()");
+        if (mDebug) Slog.v(mTag, "onDestroy()");
         // hook for subclasses
 
         // Just in case destroy came in before detach, let's take care of that now
@@ -1083,9 +1085,9 @@
      *
      * Must run on mHandler.
      */
-    private final void detach() {
+    private void detach() {
         if (mStarted) {
-            if (mDebug) Slog.v(TAG, "detach(): Calling onDreamingStopped()");
+            if (mDebug) Slog.v(mTag, "detach(): Calling onDreamingStopped()");
             mStarted = false;
             onDreamingStopped();
         }
@@ -1110,12 +1112,12 @@
      */
     private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
         if (mDreamToken != null) {
-            Slog.e(TAG, "attach() called when dream with token=" + mDreamToken
+            Slog.e(mTag, "attach() called when dream with token=" + mDreamToken
                     + " already attached");
             return;
         }
         if (mFinished || mWaking) {
-            Slog.w(TAG, "attach() called after dream already finished");
+            Slog.w(mTag, "attach() called after dream already finished");
             try {
                 mDreamManager.finishSelf(dreamToken, true /*immediate*/);
             } catch (RemoteException ex) {
@@ -1158,10 +1160,9 @@
             try {
                 if (!ActivityTaskManager.getService().startDreamActivity(i)) {
                     detach();
-                    return;
                 }
             } catch (RemoteException e) {
-                Log.w(TAG, "Could not connect to activity task manager to start dream activity");
+                Log.w(mTag, "Could not connect to activity task manager to start dream activity");
                 e.rethrowFromSystemServer();
             }
         } else {
@@ -1191,7 +1192,7 @@
         // along well. Dreams usually don't need such bars anyways, so disable them by default.
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
 
-        // Hide all insets  when the dream is showing
+        // Hide all insets when the dream is showing
         mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
         mWindow.setDecorFitsSystemWindows(false);
 
@@ -1207,7 +1208,7 @@
                             try {
                                 overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
                             } catch (RemoteException e) {
-                                Log.e(TAG, "could not send window attributes:" + e);
+                                Log.e(mTag, "could not send window attributes:" + e);
                             }
                         });
                     }
@@ -1243,17 +1244,12 @@
 
     @Override
     protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
-        DumpUtils.dumpAsync(mHandler, new Dump() {
-            @Override
-            public void dump(PrintWriter pw, String prefix) {
-                dumpOnHandler(fd, pw, args);
-            }
-        }, pw, "", 1000);
+        DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
     }
 
     /** @hide */
     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print(TAG + ": ");
+        pw.print(mTag + ": ");
         if (mFinished) {
             pw.println("stopped");
         } else {
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
similarity index 91%
rename from services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
rename to core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index c26965d..c452e06 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.selectiontoolbar;
+package android.service.selectiontoolbar;
 
-import android.service.selectiontoolbar.SelectionToolbarRenderService;
 import android.util.Log;
 import android.view.selectiontoolbar.ShowInfo;
 
 /**
  * The default implementation of {@link SelectionToolbarRenderService}.
+ *
+ *  @hide
  */
+// TODO(b/214122495): fix class not found then move to system service folder
 public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
 
     private static final String TAG = "DefaultSelectionToolbarRenderService";
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
new file mode 100644
index 0000000..95ecc4e
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -0,0 +1,1619 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.selectiontoolbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Size;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.floatingtoolbar.FloatingToolbarPopup;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A popup window used by the floating toolbar to render menu items in the local app process.
+ *
+ * This class is responsible for the rendering/animation of the floating toolbar.
+ * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
+ * to transition between panels.
+ */
+
+final class RemoteSelectionToolbar implements FloatingToolbarPopup {
+
+    /* Minimum and maximum number of items allowed in the overflow. */
+    private static final int MIN_OVERFLOW_SIZE = 2;
+    private static final int MAX_OVERFLOW_SIZE = 4;
+
+    private final Context mContext;
+    private final View mParent;  // Parent for the popup window.
+    private final PopupWindow mPopupWindow;
+
+    /* Margins between the popup window and its content. */
+    private final int mMarginHorizontal;
+    private final int mMarginVertical;
+
+    /* View components */
+    private final ViewGroup mContentContainer;  // holds all contents.
+    private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
+    // holds menu items hidden in the overflow.
+    private final OverflowPanel mOverflowPanel;
+    private final ImageButton mOverflowButton;  // opens/closes the overflow.
+    /* overflow button drawables. */
+    private final Drawable mArrow;
+    private final Drawable mOverflow;
+    private final AnimatedVectorDrawable mToArrow;
+    private final AnimatedVectorDrawable mToOverflow;
+
+    private final OverflowPanelViewHelper mOverflowPanelViewHelper;
+
+    /* Animation interpolators. */
+    private final Interpolator mLogAccelerateInterpolator;
+    private final Interpolator mFastOutSlowInInterpolator;
+    private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mFastOutLinearInInterpolator;
+
+    /* Animations. */
+    private final AnimatorSet mShowAnimation;
+    private final AnimatorSet mDismissAnimation;
+    private final AnimatorSet mHideAnimation;
+    private final AnimationSet mOpenOverflowAnimation;
+    private final AnimationSet mCloseOverflowAnimation;
+    private final Animation.AnimationListener mOverflowAnimationListener;
+
+    private final Rect mViewPortOnScreen = new Rect();  // portion of screen we can draw in.
+    private final Point mCoordsOnWindow = new Point();  // popup window coordinates.
+    /* Temporary data holders. Reset values before using. */
+    private final int[] mTmpCoords = new int[2];
+
+    private final Region mTouchableRegion = new Region();
+    private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
+            info -> {
+                info.contentInsets.setEmpty();
+                info.visibleInsets.setEmpty();
+                info.touchableRegion.set(mTouchableRegion);
+                info.setTouchableInsets(
+                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            };
+
+    private final int mLineHeight;
+    private final int mIconTextSpacing;
+
+    /**
+     * @see OverflowPanelViewHelper#preparePopupContent().
+     */
+    private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
+        @Override
+        public void run() {
+            setPanelsStatesAtRestingPosition();
+            setContentAreaAsTouchableSurface();
+            mContentContainer.setAlpha(1);
+        }
+    };
+
+    private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
+    private boolean mHidden; // tracks whether this popup is hidden or hiding.
+
+    /* Calculated sizes for panels and overflow button. */
+    private final Size mOverflowButtonSize;
+    private Size mOverflowPanelSize;  // Should be null when there is no overflow.
+    private Size mMainPanelSize;
+
+    /* Menu items and click listeners */
+    private final Map<MenuItemRepr, MenuItem> mMenuItems = new LinkedHashMap<>();
+    private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+    private final View.OnClickListener mMenuItemButtonOnClickListener =
+            new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnMenuItemClickListener == null) {
+                        return;
+                    }
+                    final Object tag = v.getTag();
+                    if (!(tag instanceof MenuItemRepr)) {
+                        return;
+                    }
+                    final MenuItem menuItem = mMenuItems.get((MenuItemRepr) tag);
+                    if (menuItem == null) {
+                        return;
+                    }
+                    mOnMenuItemClickListener.onMenuItemClick(menuItem);
+                }
+            };
+
+    private boolean mOpenOverflowUpwards;  // Whether the overflow opens upwards or downwards.
+    private boolean mIsOverflowOpen;
+
+    private int mTransitionDurationScale;  // Used to scale the toolbar transition duration.
+
+    private final Rect mPreviousContentRect = new Rect();
+    private int mSuggestedWidth;
+    private boolean mWidthChanged = true;
+
+    /**
+     * Initializes a new floating toolbar popup.
+     *
+     * @param parent  A parent view to get the {@link android.view.View#getWindowToken()} token
+     *      from.
+     */
+    RemoteSelectionToolbar(Context context, View parent) {
+        mParent = Objects.requireNonNull(parent);
+        mContext = applyDefaultTheme(context);
+        mContentContainer = createContentContainer(mContext);
+        mPopupWindow = createPopupWindow(mContentContainer);
+        mMarginHorizontal = parent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+        mMarginVertical = parent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
+        mLineHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_height);
+        mIconTextSpacing = context.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
+
+        // Interpolators
+        mLogAccelerateInterpolator = new LogAccelerateInterpolator();
+        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_slow_in);
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.linear_out_slow_in);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_linear_in);
+
+        // Drawables. Needed for views.
+        mArrow = mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
+        mArrow.setAutoMirrored(true);
+        mOverflow = mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
+        mOverflow.setAutoMirrored(true);
+        mToArrow = (AnimatedVectorDrawable) mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
+        mToArrow.setAutoMirrored(true);
+        mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
+                .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
+        mToOverflow.setAutoMirrored(true);
+
+        // Views
+        mOverflowButton = createOverflowButton();
+        mOverflowButtonSize = measure(mOverflowButton);
+        mMainPanel = createMainPanel();
+        mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
+        mOverflowPanel = createOverflowPanel();
+
+        // Animation. Need views.
+        mOverflowAnimationListener = createOverflowAnimationListener();
+        mOpenOverflowAnimation = new AnimationSet(true);
+        mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+        mCloseOverflowAnimation = new AnimationSet(true);
+        mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+        mShowAnimation = createEnterAnimation(mContentContainer);
+        mDismissAnimation = createExitAnimation(
+                mContentContainer,
+                150,  // startDelay
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mPopupWindow.dismiss();
+                        mContentContainer.removeAllViews();
+                    }
+                });
+        mHideAnimation = createExitAnimation(
+                mContentContainer,
+                0,  // startDelay
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mPopupWindow.dismiss();
+                    }
+                });
+    }
+
+    @Override
+    public boolean setOutsideTouchable(
+            boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+        boolean ret = false;
+        if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
+            mPopupWindow.setOutsideTouchable(outsideTouchable);
+            mPopupWindow.setFocusable(!outsideTouchable);
+            mPopupWindow.update();
+            ret = true;
+        }
+        mPopupWindow.setOnDismissListener(onDismiss);
+        return ret;
+    }
+
+    /**
+     * Lays out buttons for the specified menu items.
+     * Requires a subsequent call to {@link FloatingToolbar#show()} to show the items.
+     */
+    private void layoutMenuItems(
+            List<MenuItem> menuItems,
+            MenuItem.OnMenuItemClickListener menuItemClickListener,
+            int suggestedWidth) {
+        cancelOverflowAnimations();
+        clearPanels();
+        updateMenuItems(menuItems, menuItemClickListener);
+        menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
+        if (!menuItems.isEmpty()) {
+            // Add remaining items to the overflow.
+            layoutOverflowPanelItems(menuItems);
+        }
+        updatePopupSize();
+    }
+
+    /**
+     * Updates the popup's menu items without rebuilding the widget.
+     * Use in place of layoutMenuItems() when the popup's views need not be reconstructed.
+     *
+     * @see #isLayoutRequired(List<MenuItem>)
+     */
+    private void updateMenuItems(
+            List<MenuItem> menuItems, MenuItem.OnMenuItemClickListener menuItemClickListener) {
+        mMenuItems.clear();
+        for (MenuItem menuItem : menuItems) {
+            mMenuItems.put(MenuItemRepr.of(menuItem), menuItem);
+        }
+        mOnMenuItemClickListener = menuItemClickListener;
+    }
+
+    /**
+     * Returns true if this popup needs a relayout to properly render the specified menu items.
+     */
+    private boolean isLayoutRequired(List<MenuItem> menuItems) {
+        return !MenuItemRepr.reprEquals(menuItems, mMenuItems.values());
+    }
+
+    @Override
+    public void setWidthChanged(boolean widthChanged) {
+        mWidthChanged = widthChanged;
+    }
+
+    @Override
+    public void setSuggestedWidth(int suggestedWidth) {
+        // Check if there's been a substantial width spec change.
+        int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+        mWidthChanged = difference > (mSuggestedWidth * 0.2);
+        mSuggestedWidth = suggestedWidth;
+    }
+
+    @Override
+    public void show(List<MenuItem> menuItems,
+            MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
+        if (isLayoutRequired(menuItems) || mWidthChanged) {
+            dismiss();
+            layoutMenuItems(menuItems, menuItemClickListener, mSuggestedWidth);
+        } else {
+            updateMenuItems(menuItems, menuItemClickListener);
+        }
+        if (!isShowing()) {
+            show(contentRect);
+        } else if (!mPreviousContentRect.equals(contentRect)) {
+            updateCoordinates(contentRect);
+        }
+        mWidthChanged = false;
+        mPreviousContentRect.set(contentRect);
+    }
+
+    private void show(Rect contentRectOnScreen) {
+        Objects.requireNonNull(contentRectOnScreen);
+
+        if (isShowing()) {
+            return;
+        }
+
+        mHidden = false;
+        mDismissed = false;
+        cancelDismissAndHideAnimations();
+        cancelOverflowAnimations();
+
+        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+        preparePopupContent();
+        // We need to specify the position in window coordinates.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+        // specify the popup position in screen coordinates.
+        mPopupWindow.showAtLocation(
+                mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
+        setTouchableSurfaceInsetsComputer();
+        runShowAnimation();
+    }
+
+    @Override
+    public void dismiss() {
+        if (mDismissed) {
+            return;
+        }
+
+        mHidden = false;
+        mDismissed = true;
+        mHideAnimation.cancel();
+
+        runDismissAnimation();
+        setZeroTouchableSurface();
+    }
+
+    @Override
+    public void hide() {
+        if (!isShowing()) {
+            return;
+        }
+
+        mHidden = true;
+        runHideAnimation();
+        setZeroTouchableSurface();
+    }
+
+    @Override
+    public boolean isShowing() {
+        return !mDismissed && !mHidden;
+    }
+
+    @Override
+    public boolean isHidden() {
+        return mHidden;
+    }
+
+    /**
+     * Updates the coordinates of this popup.
+     * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+     * This is a no-op if this popup is not showing.
+     */
+    private void updateCoordinates(Rect contentRectOnScreen) {
+        Objects.requireNonNull(contentRectOnScreen);
+
+        if (!isShowing() || !mPopupWindow.isShowing()) {
+            return;
+        }
+
+        cancelOverflowAnimations();
+        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
+        preparePopupContent();
+        // We need to specify the position in window coordinates.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can
+        // specify the popup position in screen coordinates.
+        mPopupWindow.update(
+                mCoordsOnWindow.x, mCoordsOnWindow.y,
+                mPopupWindow.getWidth(), mPopupWindow.getHeight());
+    }
+
+    private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
+        refreshViewPort();
+
+        // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
+        // landscape.
+        final int x = Math.min(
+                contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+                mViewPortOnScreen.right - mPopupWindow.getWidth());
+
+        final int y;
+
+        final int availableHeightAboveContent =
+                contentRectOnScreen.top - mViewPortOnScreen.top;
+        final int availableHeightBelowContent =
+                mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
+
+        final int margin = 2 * mMarginVertical;
+        final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
+
+        if (!hasOverflow()) {
+            if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
+                // There is enough space at the top of the content.
+                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
+                // There is enough space at the bottom of the content.
+                y = contentRectOnScreen.bottom;
+            } else if (availableHeightBelowContent >= mLineHeight) {
+                // Just enough space to fit the toolbar with no vertical margins.
+                y = contentRectOnScreen.bottom - mMarginVertical;
+            } else {
+                // Not enough space. Prefer to position as high as possible.
+                y = Math.max(
+                        mViewPortOnScreen.top,
+                        contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
+            }
+        } else {
+            // Has an overflow.
+            final int minimumOverflowHeightWithMargin =
+                    calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
+            final int availableHeightThroughContentDown =
+                    mViewPortOnScreen.bottom - contentRectOnScreen.top
+                            + toolbarHeightWithVerticalMargin;
+            final int availableHeightThroughContentUp =
+                    contentRectOnScreen.bottom - mViewPortOnScreen.top
+                            + toolbarHeightWithVerticalMargin;
+
+            if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the top of the content rect for the overflow.
+                // Position above and open upwards.
+                updateOverflowHeight(availableHeightAboveContent - margin);
+                y = contentRectOnScreen.top - mPopupWindow.getHeight();
+                mOpenOverflowUpwards = true;
+            } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
+                    && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the top of the content rect for the main panel
+                // but not the overflow.
+                // Position above but open downwards.
+                updateOverflowHeight(availableHeightThroughContentDown - margin);
+                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+                mOpenOverflowUpwards = false;
+            } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the bottom of the content rect for the overflow.
+                // Position below and open downwards.
+                updateOverflowHeight(availableHeightBelowContent - margin);
+                y = contentRectOnScreen.bottom;
+                mOpenOverflowUpwards = false;
+            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
+                    && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
+                // There is enough space at the bottom of the content rect for the main panel
+                // but not the overflow.
+                // Position below but open upwards.
+                updateOverflowHeight(availableHeightThroughContentUp - margin);
+                y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
+                        - mPopupWindow.getHeight();
+                mOpenOverflowUpwards = true;
+            } else {
+                // Not enough space.
+                // Position at the top of the view port and open downwards.
+                updateOverflowHeight(mViewPortOnScreen.height() - margin);
+                y = mViewPortOnScreen.top;
+                mOpenOverflowUpwards = false;
+            }
+        }
+
+        // We later specify the location of PopupWindow relative to the attached window.
+        // The idea here is that 1) we can get the location of a View in both window coordinates
+        // and screen coordinates, where the offset between them should be equal to the window
+        // origin, and 2) we can use an arbitrary for this calculation while calculating the
+        // location of the rootview is supposed to be least expensive.
+        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
+        // the following calculation.
+        mParent.getRootView().getLocationOnScreen(mTmpCoords);
+        int rootViewLeftOnScreen = mTmpCoords[0];
+        int rootViewTopOnScreen = mTmpCoords[1];
+        mParent.getRootView().getLocationInWindow(mTmpCoords);
+        int rootViewLeftOnWindow = mTmpCoords[0];
+        int rootViewTopOnWindow = mTmpCoords[1];
+        int windowLeftOnScreen = rootViewLeftOnScreen - rootViewLeftOnWindow;
+        int windowTopOnScreen = rootViewTopOnScreen - rootViewTopOnWindow;
+        mCoordsOnWindow.set(
+                Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
+    }
+
+    /**
+     * Performs the "show" animation on the floating popup.
+     */
+    private void runShowAnimation() {
+        mShowAnimation.start();
+    }
+
+    /**
+     * Performs the "dismiss" animation on the floating popup.
+     */
+    private void runDismissAnimation() {
+        mDismissAnimation.start();
+    }
+
+    /**
+     * Performs the "hide" animation on the floating popup.
+     */
+    private void runHideAnimation() {
+        mHideAnimation.start();
+    }
+
+    private void cancelDismissAndHideAnimations() {
+        mDismissAnimation.cancel();
+        mHideAnimation.cancel();
+    }
+
+    private void cancelOverflowAnimations() {
+        mContentContainer.clearAnimation();
+        mMainPanel.animate().cancel();
+        mOverflowPanel.animate().cancel();
+        mToArrow.stop();
+        mToOverflow.stop();
+    }
+
+    private void openOverflow() {
+        final int targetWidth = mOverflowPanelSize.getWidth();
+        final int targetHeight = mOverflowPanelSize.getHeight();
+        final int startWidth = mContentContainer.getWidth();
+        final int startHeight = mContentContainer.getHeight();
+        final float startY = mContentContainer.getY();
+        final float left = mContentContainer.getX();
+        final float right = left + mContentContainer.getWidth();
+        Animation widthAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+                setWidth(mContentContainer, startWidth + deltaWidth);
+                if (isInRTLMode()) {
+                    mContentContainer.setX(left);
+
+                    // Lock the panels in place.
+                    mMainPanel.setX(0);
+                    mOverflowPanel.setX(0);
+                } else {
+                    mContentContainer.setX(right - mContentContainer.getWidth());
+
+                    // Offset the panels' positions so they look like they're locked in place
+                    // on the screen.
+                    mMainPanel.setX(mContentContainer.getWidth() - startWidth);
+                    mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
+                }
+            }
+        };
+        Animation heightAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+                setHeight(mContentContainer, startHeight + deltaHeight);
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(
+                            startY - (mContentContainer.getHeight() - startHeight));
+                    positionContentYCoordinatesIfOpeningOverflowUpwards();
+                }
+            }
+        };
+        final float overflowButtonStartX = mOverflowButton.getX();
+        final float overflowButtonTargetX =
+                isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth()
+                        : overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
+        Animation overflowButtonAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                float overflowButtonX = overflowButtonStartX
+                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+                float deltaContainerWidth =
+                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+                mOverflowButton.setX(actualOverflowButtonX);
+            }
+        };
+        widthAnimation.setInterpolator(mLogAccelerateInterpolator);
+        widthAnimation.setDuration(getAdjustedDuration(250));
+        heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        heightAnimation.setDuration(getAdjustedDuration(250));
+        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+        mOpenOverflowAnimation.getAnimations().clear();
+        mOpenOverflowAnimation.getAnimations().clear();
+        mOpenOverflowAnimation.addAnimation(widthAnimation);
+        mOpenOverflowAnimation.addAnimation(heightAnimation);
+        mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
+        mContentContainer.startAnimation(mOpenOverflowAnimation);
+        mIsOverflowOpen = true;
+        mMainPanel.animate()
+                .alpha(0).withLayer()
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .setDuration(250)
+                .start();
+        mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
+    }
+
+    private void closeOverflow() {
+        final int targetWidth = mMainPanelSize.getWidth();
+        final int startWidth = mContentContainer.getWidth();
+        final float left = mContentContainer.getX();
+        final float right = left + mContentContainer.getWidth();
+        Animation widthAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+                setWidth(mContentContainer, startWidth + deltaWidth);
+                if (isInRTLMode()) {
+                    mContentContainer.setX(left);
+
+                    // Lock the panels in place.
+                    mMainPanel.setX(0);
+                    mOverflowPanel.setX(0);
+                } else {
+                    mContentContainer.setX(right - mContentContainer.getWidth());
+
+                    // Offset the panels' positions so they look like they're locked in place
+                    // on the screen.
+                    mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
+                    mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
+                }
+            }
+        };
+        final int targetHeight = mMainPanelSize.getHeight();
+        final int startHeight = mContentContainer.getHeight();
+        final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+        Animation heightAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+                setHeight(mContentContainer, startHeight + deltaHeight);
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(bottom - mContentContainer.getHeight());
+                    positionContentYCoordinatesIfOpeningOverflowUpwards();
+                }
+            }
+        };
+        final float overflowButtonStartX = mOverflowButton.getX();
+        final float overflowButtonTargetX =
+                isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth()
+                        : overflowButtonStartX + startWidth - mOverflowButton.getWidth();
+        Animation overflowButtonAnimation = new Animation() {
+            @Override
+            protected void applyTransformation(float interpolatedTime, Transformation t) {
+                float overflowButtonX = overflowButtonStartX
+                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+                float deltaContainerWidth =
+                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
+                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+                mOverflowButton.setX(actualOverflowButtonX);
+            }
+        };
+        widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        widthAnimation.setDuration(getAdjustedDuration(250));
+        heightAnimation.setInterpolator(mLogAccelerateInterpolator);
+        heightAnimation.setDuration(getAdjustedDuration(250));
+        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+        overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+        mCloseOverflowAnimation.getAnimations().clear();
+        mCloseOverflowAnimation.addAnimation(widthAnimation);
+        mCloseOverflowAnimation.addAnimation(heightAnimation);
+        mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
+        mContentContainer.startAnimation(mCloseOverflowAnimation);
+        mIsOverflowOpen = false;
+        mMainPanel.animate()
+                .alpha(1).withLayer()
+                .setInterpolator(mFastOutLinearInInterpolator)
+                .setDuration(100)
+                .start();
+        mOverflowPanel.animate()
+                .alpha(0).withLayer()
+                .setInterpolator(mLinearOutSlowInInterpolator)
+                .setDuration(150)
+                .start();
+    }
+
+    /**
+     * Defines the position of the floating toolbar popup panels when transition animation has
+     * stopped.
+     */
+    private void setPanelsStatesAtRestingPosition() {
+        mOverflowButton.setEnabled(true);
+        mOverflowPanel.awakenScrollBars();
+
+        if (mIsOverflowOpen) {
+            // Set open state.
+            final Size containerSize = mOverflowPanelSize;
+            setSize(mContentContainer, containerSize);
+            mMainPanel.setAlpha(0);
+            mMainPanel.setVisibility(View.INVISIBLE);
+            mOverflowPanel.setAlpha(1);
+            mOverflowPanel.setVisibility(View.VISIBLE);
+            mOverflowButton.setImageDrawable(mArrow);
+            mOverflowButton.setContentDescription(mContext.getString(
+                    R.string.floating_toolbar_close_overflow_description));
+
+            // Update x-coordinates depending on RTL state.
+            if (isInRTLMode()) {
+                mContentContainer.setX(mMarginHorizontal);  // align left
+                mMainPanel.setX(0);  // align left
+                mOverflowButton.setX(// align right
+                        containerSize.getWidth() - mOverflowButtonSize.getWidth());
+                mOverflowPanel.setX(0);  // align left
+            } else {
+                mContentContainer.setX(// align right
+                        mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+                mMainPanel.setX(-mContentContainer.getX());  // align right
+                mOverflowButton.setX(0);  // align left
+                mOverflowPanel.setX(0);  // align left
+            }
+
+            // Update y-coordinates depending on overflow's open direction.
+            if (mOpenOverflowUpwards) {
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setY(// align bottom
+                        containerSize.getHeight() - mContentContainer.getHeight());
+                mOverflowButton.setY(// align bottom
+                        containerSize.getHeight() - mOverflowButtonSize.getHeight());
+                mOverflowPanel.setY(0);  // align top
+            } else {
+                // opens downwards.
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setY(0);  // align top
+                mOverflowButton.setY(0);  // align top
+                mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
+            }
+        } else {
+            // Overflow not open. Set closed state.
+            final Size containerSize = mMainPanelSize;
+            setSize(mContentContainer, containerSize);
+            mMainPanel.setAlpha(1);
+            mMainPanel.setVisibility(View.VISIBLE);
+            mOverflowPanel.setAlpha(0);
+            mOverflowPanel.setVisibility(View.INVISIBLE);
+            mOverflowButton.setImageDrawable(mOverflow);
+            mOverflowButton.setContentDescription(mContext.getString(
+                    R.string.floating_toolbar_open_overflow_description));
+
+            if (hasOverflow()) {
+                // Update x-coordinates depending on RTL state.
+                if (isInRTLMode()) {
+                    mContentContainer.setX(mMarginHorizontal);  // align left
+                    mMainPanel.setX(0);  // align left
+                    mOverflowButton.setX(0);  // align left
+                    mOverflowPanel.setX(0);  // align left
+                } else {
+                    mContentContainer.setX(// align right
+                            mPopupWindow.getWidth() - containerSize.getWidth() - mMarginHorizontal);
+                    mMainPanel.setX(0);  // align left
+                    mOverflowButton.setX(// align right
+                            containerSize.getWidth() - mOverflowButtonSize.getWidth());
+                    mOverflowPanel.setX(// align right
+                            containerSize.getWidth() - mOverflowPanelSize.getWidth());
+                }
+
+                // Update y-coordinates depending on overflow's open direction.
+                if (mOpenOverflowUpwards) {
+                    mContentContainer.setY(// align bottom
+                            mMarginVertical + mOverflowPanelSize.getHeight()
+                                    - containerSize.getHeight());
+                    mMainPanel.setY(0);  // align top
+                    mOverflowButton.setY(0);  // align top
+                    mOverflowPanel.setY(// align bottom
+                            containerSize.getHeight() - mOverflowPanelSize.getHeight());
+                } else {
+                    // opens downwards.
+                    mContentContainer.setY(mMarginVertical);  // align top
+                    mMainPanel.setY(0);  // align top
+                    mOverflowButton.setY(0);  // align top
+                    mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
+                }
+            } else {
+                // No overflow.
+                mContentContainer.setX(mMarginHorizontal);  // align left
+                mContentContainer.setY(mMarginVertical);  // align top
+                mMainPanel.setX(0);  // align left
+                mMainPanel.setY(0);  // align top
+            }
+        }
+    }
+
+    private void updateOverflowHeight(int suggestedHeight) {
+        if (hasOverflow()) {
+            final int maxItemSize =
+                    (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight;
+            final int newHeight = calculateOverflowHeight(maxItemSize);
+            if (mOverflowPanelSize.getHeight() != newHeight) {
+                mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
+            }
+            setSize(mOverflowPanel, mOverflowPanelSize);
+            if (mIsOverflowOpen) {
+                setSize(mContentContainer, mOverflowPanelSize);
+                if (mOpenOverflowUpwards) {
+                    final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
+                    mContentContainer.setY(mContentContainer.getY() + deltaHeight);
+                    mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
+                }
+            } else {
+                setSize(mContentContainer, mMainPanelSize);
+            }
+            updatePopupSize();
+        }
+    }
+
+    private void updatePopupSize() {
+        int width = 0;
+        int height = 0;
+        if (mMainPanelSize != null) {
+            width = Math.max(width, mMainPanelSize.getWidth());
+            height = Math.max(height, mMainPanelSize.getHeight());
+        }
+        if (mOverflowPanelSize != null) {
+            width = Math.max(width, mOverflowPanelSize.getWidth());
+            height = Math.max(height, mOverflowPanelSize.getHeight());
+        }
+        mPopupWindow.setWidth(width + mMarginHorizontal * 2);
+        mPopupWindow.setHeight(height + mMarginVertical * 2);
+        maybeComputeTransitionDurationScale();
+    }
+
+    private void refreshViewPort() {
+        mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
+    }
+
+    private int getAdjustedToolbarWidth(int suggestedWidth) {
+        int width = suggestedWidth;
+        refreshViewPort();
+        int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
+                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+        if (width <= 0) {
+            width = mParent.getResources()
+                    .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
+        }
+        return Math.min(width, maximumWidth);
+    }
+
+    /**
+     * Sets the touchable region of this popup to be zero. This means that all touch events on
+     * this popup will go through to the surface behind it.
+     */
+    private void setZeroTouchableSurface() {
+        mTouchableRegion.setEmpty();
+    }
+
+    /**
+     * Sets the touchable region of this popup to be the area occupied by its content.
+     */
+    private void setContentAreaAsTouchableSurface() {
+        Objects.requireNonNull(mMainPanelSize);
+        final int width;
+        final int height;
+        if (mIsOverflowOpen) {
+            Objects.requireNonNull(mOverflowPanelSize);
+            width = mOverflowPanelSize.getWidth();
+            height = mOverflowPanelSize.getHeight();
+        } else {
+            width = mMainPanelSize.getWidth();
+            height = mMainPanelSize.getHeight();
+        }
+        mTouchableRegion.set(
+                (int) mContentContainer.getX(),
+                (int) mContentContainer.getY(),
+                (int) mContentContainer.getX() + width,
+                (int) mContentContainer.getY() + height);
+    }
+
+    /**
+     * Make the touchable area of this popup be the area specified by mTouchableRegion.
+     * This should be called after the popup window has been dismissed (dismiss/hide)
+     * and is probably being re-shown with a new content root view.
+     */
+    private void setTouchableSurfaceInsetsComputer() {
+        ViewTreeObserver viewTreeObserver = mPopupWindow.getContentView()
+                .getRootView()
+                .getViewTreeObserver();
+        viewTreeObserver.removeOnComputeInternalInsetsListener(mInsetsComputer);
+        viewTreeObserver.addOnComputeInternalInsetsListener(mInsetsComputer);
+    }
+
+    private boolean isInRTLMode() {
+        return mContext.getApplicationInfo().hasRtlSupport()
+                && mContext.getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_RTL;
+    }
+
+    private boolean hasOverflow() {
+        return mOverflowPanelSize != null;
+    }
+
+    /**
+     * Fits as many menu items in the main panel and returns a list of the menu items that
+     * were not fit in.
+     *
+     * @return The menu items that are not included in this main panel.
+     */
+    public List<MenuItem> layoutMainPanelItems(
+            List<MenuItem> menuItems, final int toolbarWidth) {
+        Objects.requireNonNull(menuItems);
+
+        int availableWidth = toolbarWidth;
+
+        final LinkedList<MenuItem> remainingMenuItems = new LinkedList<>();
+        // add the overflow menu items to the end of the remainingMenuItems list.
+        final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
+        for (MenuItem menuItem : menuItems) {
+            if (menuItem.getItemId() != android.R.id.textAssist
+                    && menuItem.requiresOverflow()) {
+                overflowMenuItems.add(menuItem);
+            } else {
+                remainingMenuItems.add(menuItem);
+            }
+        }
+        remainingMenuItems.addAll(overflowMenuItems);
+
+        mMainPanel.removeAllViews();
+        mMainPanel.setPaddingRelative(0, 0, 0, 0);
+
+        int lastGroupId = -1;
+        boolean isFirstItem = true;
+        while (!remainingMenuItems.isEmpty()) {
+            final MenuItem menuItem = remainingMenuItems.peek();
+
+            // if this is the first item, regardless of requiresOverflow(), it should be
+            // displayed on the main panel. Otherwise all items including this one will be
+            // overflow items, and should be displayed in overflow panel.
+            if (!isFirstItem && menuItem.requiresOverflow()) {
+                break;
+            }
+
+            final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+            final View menuItemButton = createMenuItemButton(
+                    mContext, menuItem, mIconTextSpacing, showIcon);
+            if (!showIcon && menuItemButton instanceof LinearLayout) {
+                ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
+            }
+
+            // Adding additional start padding for the first button to even out button spacing.
+            if (isFirstItem) {
+                menuItemButton.setPaddingRelative(
+                        (int) (1.5 * menuItemButton.getPaddingStart()),
+                        menuItemButton.getPaddingTop(),
+                        menuItemButton.getPaddingEnd(),
+                        menuItemButton.getPaddingBottom());
+            }
+
+            // Adding additional end padding for the last button to even out button spacing.
+            boolean isLastItem = remainingMenuItems.size() == 1;
+            if (isLastItem) {
+                menuItemButton.setPaddingRelative(
+                        menuItemButton.getPaddingStart(),
+                        menuItemButton.getPaddingTop(),
+                        (int) (1.5 * menuItemButton.getPaddingEnd()),
+                        menuItemButton.getPaddingBottom());
+            }
+
+            menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+            final int menuItemButtonWidth = Math.min(
+                    menuItemButton.getMeasuredWidth(), toolbarWidth);
+
+            // Check if we can fit an item while reserving space for the overflowButton.
+            final boolean canFitWithOverflow =
+                    menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
+            final boolean canFitNoOverflow =
+                    isLastItem && menuItemButtonWidth <= availableWidth;
+            if (canFitWithOverflow || canFitNoOverflow) {
+                setButtonTagAndClickListener(menuItemButton, menuItem);
+                // Set tooltips for main panel items, but not overflow items (b/35726766).
+                menuItemButton.setTooltipText(menuItem.getTooltipText());
+                mMainPanel.addView(menuItemButton);
+                final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+                params.width = menuItemButtonWidth;
+                menuItemButton.setLayoutParams(params);
+                availableWidth -= menuItemButtonWidth;
+                remainingMenuItems.pop();
+            } else {
+                break;
+            }
+            lastGroupId = menuItem.getGroupId();
+            isFirstItem = false;
+        }
+
+        if (!remainingMenuItems.isEmpty()) {
+            // Reserve space for overflowButton.
+            mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
+        }
+
+        mMainPanelSize = measure(mMainPanel);
+        return remainingMenuItems;
+    }
+
+    private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
+        ArrayAdapter<MenuItem> overflowPanelAdapter =
+                (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+        overflowPanelAdapter.clear();
+        final int size = menuItems.size();
+        for (int i = 0; i < size; i++) {
+            overflowPanelAdapter.add(menuItems.get(i));
+        }
+        mOverflowPanel.setAdapter(overflowPanelAdapter);
+        if (mOpenOverflowUpwards) {
+            mOverflowPanel.setY(0);
+        } else {
+            mOverflowPanel.setY(mOverflowButtonSize.getHeight());
+        }
+
+        int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
+        int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
+        mOverflowPanelSize = new Size(width, height);
+        setSize(mOverflowPanel, mOverflowPanelSize);
+    }
+
+    /**
+     * Resets the content container and appropriately position it's panels.
+     */
+    private void preparePopupContent() {
+        mContentContainer.removeAllViews();
+
+        // Add views in the specified order so they stack up as expected.
+        // Order: overflowPanel, mainPanel, overflowButton.
+        if (hasOverflow()) {
+            mContentContainer.addView(mOverflowPanel);
+        }
+        mContentContainer.addView(mMainPanel);
+        if (hasOverflow()) {
+            mContentContainer.addView(mOverflowButton);
+        }
+        setPanelsStatesAtRestingPosition();
+        setContentAreaAsTouchableSurface();
+
+        // The positioning of contents in RTL is wrong when the view is first rendered.
+        // Hide the view and post a runnable to recalculate positions and render the view.
+        // TODO: Investigate why this happens and fix.
+        if (isInRTLMode()) {
+            mContentContainer.setAlpha(0);
+            mContentContainer.post(mPreparePopupContentRTLHelper);
+        }
+    }
+
+    /**
+     * Clears out the panels and their container. Resets their calculated sizes.
+     */
+    private void clearPanels() {
+        mOverflowPanelSize = null;
+        mMainPanelSize = null;
+        mIsOverflowOpen = false;
+        mMainPanel.removeAllViews();
+        ArrayAdapter<MenuItem> overflowPanelAdapter =
+                (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+        overflowPanelAdapter.clear();
+        mOverflowPanel.setAdapter(overflowPanelAdapter);
+        mContentContainer.removeAllViews();
+    }
+
+    private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
+        if (mOpenOverflowUpwards) {
+            mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
+            mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
+            mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
+        }
+    }
+
+    private int getOverflowWidth() {
+        int overflowWidth = 0;
+        final int count = mOverflowPanel.getAdapter().getCount();
+        for (int i = 0; i < count; i++) {
+            MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+            overflowWidth =
+                    Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
+        }
+        return overflowWidth;
+    }
+
+    private int calculateOverflowHeight(int maxItemSize) {
+        // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
+        int actualSize = Math.min(
+                MAX_OVERFLOW_SIZE,
+                Math.min(
+                        Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
+                        mOverflowPanel.getCount()));
+        int extension = 0;
+        if (actualSize < mOverflowPanel.getCount()) {
+            // The overflow will require scrolling to get to all the items.
+            // Extend the height so that part of the hidden items is displayed.
+            extension = (int) (mLineHeight * 0.5f);
+        }
+        return actualSize * mLineHeight
+                + mOverflowButtonSize.getHeight()
+                + extension;
+    }
+
+    private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
+        menuItemButton.setTag(MenuItemRepr.of(menuItem));
+        menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+    }
+
+    /**
+     * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
+     * animations. See comment about this in the code.
+     */
+    private int getAdjustedDuration(int originalDuration) {
+        if (mTransitionDurationScale < 150) {
+            // For smaller transition, decrease the time.
+            return Math.max(originalDuration - 50, 0);
+        } else if (mTransitionDurationScale > 300) {
+            // For bigger transition, increase the time.
+            return originalDuration + 50;
+        }
+
+        // Scale the animation duration with getDurationScale(). This allows
+        // android.view.animation.* animations to scale just like android.animation.* animations
+        // when  animator duration scale is adjusted in "Developer Options".
+        // For this reason, do not use this method for android.animation.* animations.
+        return (int) (originalDuration * ValueAnimator.getDurationScale());
+    }
+
+    private void maybeComputeTransitionDurationScale() {
+        if (mMainPanelSize != null && mOverflowPanelSize != null) {
+            int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
+            int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
+            mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h)
+                    / mContentContainer.getContext().getResources().getDisplayMetrics().density);
+        }
+    }
+
+    private ViewGroup createMainPanel() {
+        ViewGroup mainPanel = new LinearLayout(mContext) {
+            @Override
+            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+                if (isOverflowAnimating()) {
+                    // Update widthMeasureSpec to make sure that this view is not clipped
+                    // as we offset its coordinates with respect to its parent.
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                            mMainPanelSize.getWidth(),
+                            MeasureSpec.EXACTLY);
+                }
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+
+            @Override
+            public boolean onInterceptTouchEvent(MotionEvent ev) {
+                // Intercept the touch event while the overflow is animating.
+                return isOverflowAnimating();
+            }
+        };
+        return mainPanel;
+    }
+
+    private ImageButton createOverflowButton() {
+        final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
+                .inflate(R.layout.floating_popup_overflow_button, null);
+        overflowButton.setImageDrawable(mOverflow);
+        overflowButton.setOnClickListener(v -> {
+            if (mIsOverflowOpen) {
+                overflowButton.setImageDrawable(mToOverflow);
+                mToOverflow.start();
+                closeOverflow();
+            } else {
+                overflowButton.setImageDrawable(mToArrow);
+                mToArrow.start();
+                openOverflow();
+            }
+        });
+        return overflowButton;
+    }
+
+    private OverflowPanel createOverflowPanel() {
+        final OverflowPanel overflowPanel = new OverflowPanel(this);
+        overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        overflowPanel.setDivider(null);
+        overflowPanel.setDividerHeight(0);
+
+        final ArrayAdapter adapter =
+                new ArrayAdapter<MenuItem>(mContext, 0) {
+                    @Override
+                    public View getView(int position, View convertView, ViewGroup parent) {
+                        return mOverflowPanelViewHelper.getView(
+                                getItem(position), mOverflowPanelSize.getWidth(), convertView);
+                    }
+                };
+        overflowPanel.setAdapter(adapter);
+
+        overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
+            MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
+            if (mOnMenuItemClickListener != null) {
+                mOnMenuItemClickListener.onMenuItemClick(menuItem);
+            }
+        });
+
+        return overflowPanel;
+    }
+
+    private boolean isOverflowAnimating() {
+        final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
+                && !mOpenOverflowAnimation.hasEnded();
+        final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
+                && !mCloseOverflowAnimation.hasEnded();
+        return overflowOpening || overflowClosing;
+    }
+
+    private Animation.AnimationListener createOverflowAnimationListener() {
+        Animation.AnimationListener listener = new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                // Disable the overflow button while it's animating.
+                // It will be re-enabled when the animation stops.
+                mOverflowButton.setEnabled(false);
+                // Ensure both panels have visibility turned on when the overflow animation
+                // starts.
+                mMainPanel.setVisibility(View.VISIBLE);
+                mOverflowPanel.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                // Posting this because it seems like this is called before the animation
+                // actually ends.
+                mContentContainer.post(() -> {
+                    setPanelsStatesAtRestingPosition();
+                    setContentAreaAsTouchableSurface();
+                });
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        };
+        return listener;
+    }
+
+    private static Size measure(View view) {
+        Preconditions.checkState(view.getParent() == null);
+        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
+    }
+
+    private static void setSize(View view, int width, int height) {
+        view.setMinimumWidth(width);
+        view.setMinimumHeight(height);
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
+        params.width = width;
+        params.height = height;
+        view.setLayoutParams(params);
+    }
+
+    private static void setSize(View view, Size size) {
+        setSize(view, size.getWidth(), size.getHeight());
+    }
+
+    private static void setWidth(View view, int width) {
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        setSize(view, width, params.height);
+    }
+
+    private static void setHeight(View view, int height) {
+        ViewGroup.LayoutParams params = view.getLayoutParams();
+        setSize(view, params.width, height);
+    }
+
+    /**
+     * A custom ListView for the overflow panel.
+     */
+    private static final class OverflowPanel extends ListView {
+
+        private final RemoteSelectionToolbar mPopup;
+
+        OverflowPanel(RemoteSelectionToolbar popup) {
+            super(Objects.requireNonNull(popup).mContext);
+            this.mPopup = popup;
+            setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
+            setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            // Update heightMeasureSpec to make sure that this view is not clipped
+            // as we offset it's coordinates with respect to its parent.
+            int height = mPopup.mOverflowPanelSize.getHeight()
+                    - mPopup.mOverflowButtonSize.getHeight();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            if (mPopup.isOverflowAnimating()) {
+                // Eat the touch event.
+                return true;
+            }
+            return super.dispatchTouchEvent(ev);
+        }
+
+        @Override
+        protected boolean awakenScrollBars() {
+            return super.awakenScrollBars();
+        }
+    }
+
+    /**
+     * A custom interpolator used for various floating toolbar animations.
+     */
+    private static final class LogAccelerateInterpolator implements Interpolator {
+
+        private static final int BASE = 100;
+        private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
+
+        private static float computeLog(float t, int base) {
+            return (float) (1 - Math.pow(base, -t));
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
+        }
+    }
+
+    /**
+     * A helper for generating views for the overflow panel.
+     */
+    private static final class OverflowPanelViewHelper {
+
+        private final View mCalculator;
+        private final int mIconTextSpacing;
+        private final int mSidePadding;
+
+        private final Context mContext;
+
+        OverflowPanelViewHelper(Context context, int iconTextSpacing) {
+            mContext = Objects.requireNonNull(context);
+            mIconTextSpacing = iconTextSpacing;
+            mSidePadding = context.getResources()
+                    .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
+            mCalculator = createMenuButton(null);
+        }
+
+        public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
+            Objects.requireNonNull(menuItem);
+            if (convertView != null) {
+                updateMenuItemButton(
+                        convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            } else {
+                convertView = createMenuButton(menuItem);
+            }
+            convertView.setMinimumWidth(minimumWidth);
+            return convertView;
+        }
+
+        public int calculateWidth(MenuItem menuItem) {
+            updateMenuItemButton(
+                    mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            mCalculator.measure(
+                    View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+            return mCalculator.getMeasuredWidth();
+        }
+
+        private View createMenuButton(MenuItem menuItem) {
+            View button = createMenuItemButton(
+                    mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
+            button.setPadding(mSidePadding, 0, mSidePadding, 0);
+            return button;
+        }
+
+        private boolean shouldShowIcon(MenuItem menuItem) {
+            if (menuItem != null) {
+                return menuItem.getGroupId() == android.R.id.textAssist;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Creates and returns a menu button for the specified menu item.
+     */
+    private static View createMenuItemButton(
+            Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final View menuItemButton = LayoutInflater.from(context)
+                .inflate(R.layout.floating_popup_menu_button, null);
+        if (menuItem != null) {
+            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
+        }
+        return menuItemButton;
+    }
+
+    /**
+     * Updates the specified menu item button with the specified menu item data.
+     */
+    private static void updateMenuItemButton(
+            View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final TextView buttonText = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_text);
+        buttonText.setEllipsize(null);
+        if (TextUtils.isEmpty(menuItem.getTitle())) {
+            buttonText.setVisibility(View.GONE);
+        } else {
+            buttonText.setVisibility(View.VISIBLE);
+            buttonText.setText(menuItem.getTitle());
+        }
+        final ImageView buttonIcon = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_image);
+        if (menuItem.getIcon() == null || !showIcon) {
+            buttonIcon.setVisibility(View.GONE);
+            if (buttonText != null) {
+                buttonText.setPaddingRelative(0, 0, 0, 0);
+            }
+        } else {
+            buttonIcon.setVisibility(View.VISIBLE);
+            buttonIcon.setImageDrawable(menuItem.getIcon());
+            if (buttonText != null) {
+                buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
+            }
+        }
+        final CharSequence contentDescription = menuItem.getContentDescription();
+        if (TextUtils.isEmpty(contentDescription)) {
+            menuItemButton.setContentDescription(menuItem.getTitle());
+        } else {
+            menuItemButton.setContentDescription(contentDescription);
+        }
+    }
+
+    private static ViewGroup createContentContainer(Context context) {
+        ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
+                .inflate(R.layout.floating_popup_container, null);
+        contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        contentContainer.setTag("floating_toolbar");
+        contentContainer.setClipToOutline(true);
+        return contentContainer;
+    }
+
+    private static PopupWindow createPopupWindow(ViewGroup content) {
+        ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+        PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+        // TODO: Use .setIsLaidOutInScreen(true) instead of .setClippingEnabled(false)
+        // unless FLAG_LAYOUT_IN_SCREEN has any unintentional side-effects.
+        popupWindow.setClippingEnabled(false);
+        popupWindow.setWindowLayoutType(
+                WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
+        popupWindow.setAnimationStyle(0);
+        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+        content.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        popupContentHolder.addView(content);
+        return popupWindow;
+    }
+
+    /**
+     * Creates an "appear" animation for the specified view.
+     *
+     * @param view  The view to animate
+     */
+    private static AnimatorSet createEnterAnimation(View view) {
+        AnimatorSet animation = new AnimatorSet();
+        animation.playTogether(
+                ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
+        return animation;
+    }
+
+    /**
+     * Creates a "disappear" animation for the specified view.
+     *
+     * @param view  The view to animate
+     * @param startDelay  The start delay of the animation
+     * @param listener  The animation listener
+     */
+    private static AnimatorSet createExitAnimation(
+            View view, int startDelay, Animator.AnimatorListener listener) {
+        AnimatorSet animation =  new AnimatorSet();
+        animation.playTogether(
+                ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
+        animation.setStartDelay(startDelay);
+        animation.addListener(listener);
+        return animation;
+    }
+
+    /**
+     * Returns a re-themed context with controlled look and feel for views.
+     */
+    private static Context applyDefaultTheme(Context originalContext) {
+        TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+        boolean isLightTheme = a.getBoolean(0, true);
+        int themeId =
+                isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
+        a.recycle();
+        return new ContextThemeWrapper(originalContext, themeId);
+    }
+
+    /**
+     * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
+     */
+    @VisibleForTesting
+    public static final class MenuItemRepr {
+
+        public final int itemId;
+        public final int groupId;
+        @Nullable public final String title;
+        @Nullable private final Drawable mIcon;
+
+        private MenuItemRepr(
+                int itemId, int groupId, @Nullable CharSequence title, @Nullable Drawable icon) {
+            this.itemId = itemId;
+            this.groupId = groupId;
+            this.title = (title == null) ? null : title.toString();
+            mIcon = icon;
+        }
+
+        /**
+         * Creates an instance of MenuItemRepr for the specified menu item.
+         */
+        public static MenuItemRepr of(MenuItem menuItem) {
+            return new MenuItemRepr(
+                    menuItem.getItemId(),
+                    menuItem.getGroupId(),
+                    menuItem.getTitle(),
+                    menuItem.getIcon());
+        }
+
+        /**
+         * Returns this object's hashcode.
+         */
+        @Override
+        public int hashCode() {
+            return Objects.hash(itemId, groupId, title, mIcon);
+        }
+
+        /**
+         * Returns true if this object is the same as the specified object.
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (!(o instanceof MenuItemRepr)) {
+                return false;
+            }
+            final MenuItemRepr other = (MenuItemRepr) o;
+            return itemId == other.itemId
+                    && groupId == other.groupId
+                    && TextUtils.equals(title, other.title)
+                    // Many Drawables (icons) do not implement equals(). Using equals() here instead
+                    // of reference comparisons in case a Drawable subclass implements equals().
+                    && Objects.equals(mIcon, other.mIcon);
+        }
+
+        /**
+         * Returns true if the two menu item collections are the same based on MenuItemRepr.
+         */
+        public static boolean reprEquals(
+                Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
+            if (menuItems1.size() != menuItems2.size()) {
+                return false;
+            }
+
+            final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
+            for (MenuItem menuItem1 : menuItems1) {
+                final MenuItem menuItem2 = menuItems2Iter.next();
+                if (!MenuItemRepr.of(menuItem1).equals(MenuItemRepr.of(menuItem2))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index dd4355d..f2a0355 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -87,6 +87,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowLayout;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
@@ -389,8 +390,9 @@
             public void resized(ClientWindowFrames frames, boolean reportDraw,
                     MergedConfiguration mergedConfiguration, boolean forceLayout,
                     boolean alwaysConsumeSystemBars, int displayId) {
-                Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
-                        reportDraw ? 1 : 0);
+                Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
+                        reportDraw ? 1 : 0,
+                        mergedConfiguration);
                 mCaller.sendMessage(msg);
             }
 
@@ -1028,6 +1030,10 @@
             }
         }
 
+        private void updateConfiguration(MergedConfiguration mergedConfiguration) {
+            mMergedConfiguration.setTo(mergedConfiguration);
+        }
+
         void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
             if (mDestroyed) {
                 Log.w(TAG, "Ignoring updateSurface due to destroyed");
@@ -1066,8 +1072,6 @@
                     mLayout.x = 0;
                     mLayout.y = 0;
 
-                    mLayout.width = myWidth;
-                    mLayout.height = myHeight;
                     mLayout.format = mFormat;
 
                     mCurWindowFlags = mWindowFlags;
@@ -1076,6 +1080,23 @@
                             | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                             | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                             | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+
+                    final Configuration config = mMergedConfiguration.getMergedConfiguration();
+                    final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+                    if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
+                            && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                        mLayout.width = myWidth;
+                        mLayout.height = myHeight;
+                        mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SCALED;
+                    } else {
+                        final float layoutScale = Math.max(
+                                maxBounds.width() / (float) myWidth,
+                                maxBounds.height() / (float) myHeight);
+                        mLayout.width = (int) (myWidth * layoutScale + .5f);
+                        mLayout.height = (int) (myHeight * layoutScale + .5f);
+                        mLayout.flags |= WindowManager.LayoutParams.FLAG_SCALED;
+                    }
+
                     mCurWindowPrivateFlags = mWindowPrivateFlags;
                     mLayout.privateFlags = mWindowPrivateFlags;
 
@@ -1121,12 +1142,14 @@
 
                     final int relayoutResult = mSession.relayout(
                             mWindow, mLayout, mWidth, mHeight,
-                            View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
-                            mInsetsState, mTempControls, mSurfaceSize);
+                            View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
+                            mInsetsState, mTempControls);
 
                     final int transformHint = SurfaceControl.rotationToBufferTransform(
                             (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
                     mSurfaceControl.setTransformHint(transformHint);
+                    WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
+                            mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
 
                     if (mSurfaceControl.isValid()) {
                         if (mBbqSurfaceControl == null) {
@@ -1164,7 +1187,6 @@
                     int h = mWinFrames.frame.height();
 
                     final DisplayCutout rawCutout = mInsetsState.getDisplayCutout();
-                    final Configuration config = getResources().getConfiguration();
                     final Rect visibleFrame = new Rect(mWinFrames.frame);
                     visibleFrame.intersect(mInsetsState.getDisplayFrame());
                     WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame,
@@ -2321,6 +2343,7 @@
                 } break;
                 case MSG_WINDOW_RESIZED: {
                     final boolean reportDraw = message.arg1 != 0;
+                    mEngine.updateConfiguration(((MergedConfiguration) message.obj));
                     mEngine.updateSurface(true, false, reportDraw);
                     mEngine.doOffsetsChanged(true);
                     mEngine.scaleAndCropScreenshot();
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index ce63376..be66db2 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -363,6 +363,9 @@
         public String toString() {
             int lineBreakStyle = (mLineBreakConfig != null)
                     ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
+            int lineBreakWordStyle = (mLineBreakConfig != null)
+                    ? mLineBreakConfig.getLineBreakWordStyle()
+                            : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
             return "{"
                 + "textSize=" + mPaint.getTextSize()
                 + ", textScaleX=" + mPaint.getTextScaleX()
@@ -376,6 +379,7 @@
                 + ", breakStrategy=" + mBreakStrategy
                 + ", hyphenationFrequency=" + mHyphenationFrequency
                 + ", lineBreakStyle=" + lineBreakStyle
+                + ", lineBreakWordStyle=" + lineBreakWordStyle
                 + "}";
         }
     };
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 69af2a5..bd468d9 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -17,7 +17,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.UiThread;
+import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 
 /**
@@ -124,4 +126,16 @@
     default void removeOnBufferTransformHintChangedListener(
             @NonNull OnBufferTransformHintChangedListener listener) {
     }
+
+    /**
+     * Sets the touchable region for this SurfaceControl, expressed in surface local
+     * coordinates. By default the touchable region is the entire Layer, indicating
+     * that if the layer is otherwise eligible to receive touch it receives touch
+     * on the entire surface. Setting the touchable region allows the SurfaceControl
+     * to receive touch in some regions, while allowing for pass-through in others.
+     *
+     * @param r The region to use or null to use the entire Layer bounds
+     */
+    default void setTouchableRegion(@Nullable Region r) {
+    }
 }
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 9889eaa..9b36b9b 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -53,6 +53,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -386,6 +387,33 @@
      * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
      *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
      * @param waterfallInsets the insets for the curved areas in waterfall display.
+     * @param info the cutout path parser info.
+     * @hide
+     */
+    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
+            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
+            @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info) {
+        this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                info, true);
+    }
+
+    /**
+     * Creates a DisplayCutout instance.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link DisplayCutout} obtained from the system.</p>
+     *
+     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
+     *                   {@link #getSafeInsetTop()} etc.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
      */
     public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
             @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
@@ -1098,6 +1126,85 @@
     }
 
     /**
+     * @return a copy of this cutout that has been rotated for a display in toRotation.
+     * @hide
+     */
+    public DisplayCutout getRotated(int startWidth, int startHeight,
+            int fromRotation, int toRotation) {
+        if (this == DisplayCutout.NO_CUTOUT) {
+            return DisplayCutout.NO_CUTOUT;
+        }
+        final int rotation = RotationUtils.deltaRotation(fromRotation, toRotation);
+        if (rotation == ROTATION_0) {
+            return this;
+        }
+        final Insets waterfallInsets = RotationUtils.rotateInsets(getWaterfallInsets(), rotation);
+        // returns a copy
+        final Rect[] newBounds = getBoundingRectsAll();
+        final Rect displayBounds = new Rect(0, 0, startWidth, startHeight);
+        for (int i = 0; i < newBounds.length; ++i) {
+            if (newBounds[i].isEmpty()) continue;
+            RotationUtils.rotateBounds(newBounds[i], displayBounds, rotation);
+        }
+        Collections.rotate(Arrays.asList(newBounds), -rotation);
+        final CutoutPathParserInfo info = getCutoutPathParserInfo();
+        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
+                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+                info.getCutoutSpec(), toRotation, info.getScale());
+        final boolean swapAspect = (rotation % 2) != 0;
+        final int endWidth = swapAspect ? startHeight : startWidth;
+        final int endHeight = swapAspect ? startWidth : startHeight;
+        final DisplayCutout tmp =
+                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo);
+        final Rect safeInsets = DisplayCutout.computeSafeInsets(endWidth, endHeight, tmp);
+        return tmp.replaceSafeInsets(safeInsets);
+    }
+
+    /**
+     * Compute the insets derived from a cutout. This is usually used to populate the safe-insets
+     * of the cutout via {@link #replaceSafeInsets}.
+     * @hide
+     */
+    public static Rect computeSafeInsets(int displayW, int displayH, DisplayCutout cutout) {
+        if (displayW == displayH) {
+            throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
+                    + displayH + " cutout=" + cutout);
+        }
+
+        int leftInset = Math.max(cutout.getWaterfallInsets().left, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectLeft(), Gravity.LEFT));
+        int topInset = Math.max(cutout.getWaterfallInsets().top, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectTop(), Gravity.TOP));
+        int rightInset = Math.max(cutout.getWaterfallInsets().right, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectRight(), Gravity.RIGHT));
+        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, findCutoutInsetForSide(
+                displayW, displayH, cutout.getBoundingRectBottom(), Gravity.BOTTOM));
+
+        return new Rect(leftInset, topInset, rightInset, bottomInset);
+    }
+
+    private static int findCutoutInsetForSide(int displayW, int displayH, Rect boundingRect,
+            int gravity) {
+        if (boundingRect.isEmpty()) {
+            return 0;
+        }
+
+        int inset = 0;
+        switch (gravity) {
+            case Gravity.TOP:
+                return Math.max(inset, boundingRect.bottom);
+            case Gravity.BOTTOM:
+                return Math.max(inset, displayH - boundingRect.top);
+            case Gravity.LEFT:
+                return Math.max(inset, boundingRect.right);
+            case Gravity.RIGHT:
+                return Math.max(inset, displayW - boundingRect.left);
+            default:
+                throw new IllegalArgumentException("unknown gravity: " + gravity);
+        }
+    }
+
+    /**
      * Helper class for passing {@link DisplayCutout} through binder.
      *
      * Needed, because {@code readFromParcel} cannot be used with immutable classes.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index fb848ad..c5ccc18 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -541,6 +541,11 @@
     void stopWindowTrace();
 
     /**
+    * If window tracing is active, saves the window trace to file, otherwise does nothing
+    */
+    void saveWindowTraceToFile();
+
+    /**
      * Returns true if window trace is enabled.
      */
     boolean isWindowTraceEnabled();
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 6226566..ccf1e44 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -74,7 +74,6 @@
      * @param viewVisibility Window root view's visibility.
      * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
      * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
-     * @param frameNumber A frame number in which changes requested in this layout will be rendered.
      * @param outFrame Rect in which is placed the new position/size on
      * screen.
      * @param outContentInsets Rect in which is placed the offsets from
@@ -97,17 +96,15 @@
      * since it was last displayed.
      * @param outSurface Object in which is placed the new display surface.
      * @param insetsState The current insets state in the system.
-     * @param outSurfaceSize The width and height of the surface control
      *
      * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
      * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
      */
     int relayout(IWindow window, in WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility,
-            int flags, long frameNumber, out ClientWindowFrames outFrames,
+            int flags, out ClientWindowFrames outFrames,
             out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
-            out InsetsState insetsState, out InsetsSourceControl[] activeControls,
-            out Point outSurfaceSize);
+            out InsetsState insetsState, out InsetsSourceControl[] activeControls);
 
     /*
      * Notify the window manager that an application is relaunching and
@@ -342,4 +339,9 @@
      * @param callback The {@link IOnBackInvokedCallback} to set.
      */
     oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback);
+
+    /**
+     * Clears a touchable region set by {@link #setInsets}.
+     */
+    void clearTouchableRegion(IWindow window);
 }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 4f1354d..188d745 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -572,6 +572,8 @@
      * @return The identifier object for this device
      * @hide
      */
+    @TestApi
+    @NonNull
     public InputDeviceIdentifier getIdentifier() {
         return mIdentifier;
     }
@@ -735,6 +737,21 @@
     }
 
     /**
+     * Gets the key code produced by the specified location on a US keyboard layout.
+     * Key code as defined in {@link android.view.KeyEvent}.
+     * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available
+     * which can alter their key mapping using country specific keyboard layouts.
+     *
+     * @param locationKeyCode The location of a key on a US keyboard layout.
+     * @return The key code produced when pressing the key at the specified location, given the
+     *         active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested
+     *         mapping could not be determined, or if an error occurred.
+     */
+    public int getKeyCodeForKeyLocation(int locationKeyCode) {
+        return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode);
+    }
+
+    /**
      * Gets information about the range of values for a particular {@link MotionEvent} axis.
      * If the device supports multiple sources, the same axis may have different meanings
      * for each source.  Returns information about the first axis found for any source.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 97b5a31..1496a4a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -52,6 +52,7 @@
 import static android.view.ViewRootImplProto.WIDTH;
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
 import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -83,6 +84,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -471,6 +474,9 @@
     final Region mTransparentRegion;
     final Region mPreviousTransparentRegion;
 
+    Region mTouchableRegion;
+    Region mPreviousTouchableRegion;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mWidth;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2868,9 +2874,9 @@
                 }
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                 final boolean freeformResizing = (relayoutResult
-                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+                        & RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
                 final boolean dockedResizing = (relayoutResult
-                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+                        & RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
                 final boolean dragResizing = freeformResizing || dockedResizing;
                 if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
                     if (DEBUG_BLAST) {
@@ -3249,9 +3255,15 @@
             mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
         }
 
+        Rect contentInsets = null;
+        Rect visibleInsets = null;
+        Region touchableRegion = null;
+        int touchableInsetMode = TOUCHABLE_INSETS_REGION;
+        boolean computedInternalInsets = false;
         if (computesInternalInsets) {
-            // Clear the original insets.
             final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
+
+            // Clear the original insets.
             insets.reset();
 
             // Compute new insets in place.
@@ -3263,9 +3275,6 @@
                 mLastGivenInsets.set(insets);
 
                 // Translate insets to screen coordinates if needed.
-                final Rect contentInsets;
-                final Rect visibleInsets;
-                final Region touchableRegion;
                 if (mTranslator != null) {
                     contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
                     visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
@@ -3275,12 +3284,46 @@
                     visibleInsets = insets.visibleInsets;
                     touchableRegion = insets.touchableRegion;
                 }
-
-                try {
-                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
-                            contentInsets, visibleInsets, touchableRegion);
-                } catch (RemoteException e) {
+                computedInternalInsets = true;
+            }
+            touchableInsetMode = insets.mTouchableInsets;
+        }
+        boolean needsSetInsets = computedInternalInsets;
+        needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
+            (mTouchableRegion != null);
+        if (needsSetInsets) {
+            if (mTouchableRegion != null) {
+                if (mPreviousTouchableRegion == null) {
+                    mPreviousTouchableRegion = new Region();
                 }
+                mPreviousTouchableRegion.set(mTouchableRegion);
+                if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
+                    Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
+                          " from OnComputeInternalInsets, while also using setTouchableRegion" +
+                          " causes setTouchableRegion to be ignored");
+                }
+            } else {
+                mPreviousTouchableRegion = null;
+            }
+            if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
+            if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
+            if (touchableRegion == null) {
+                touchableRegion = mTouchableRegion;
+            } else if (touchableRegion != null && mTouchableRegion != null) {
+                touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
+            }
+            try {
+                mWindowSession.setInsets(mWindow, touchableInsetMode,
+                                         contentInsets, visibleInsets, touchableRegion);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
+            mPreviousTouchableRegion = null;
+            try {
+                mWindowSession.clearTouchableRegion(mWindow);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
         }
 
@@ -7928,22 +7971,25 @@
             }
         }
 
-        long frameNumber = -1;
-        if (mSurface.isValid()) {
-            frameNumber = mSurface.getNextFrameNumber();
-        }
+        final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
+        final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
 
         int relayoutResult = mWindowSession.relayout(mWindow, params,
-                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
-                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
-                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
+                requestedWidth, requestedHeight, viewVisibility,
+                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                 mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
-                mTempControls, mSurfaceSize);
+                mTempControls);
 
         final int transformHint = SurfaceControl.rotationToBufferTransform(
                 (mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
         mSurfaceControl.setTransformHint(transformHint);
 
+        final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
+        final boolean dragResizing = (relayoutResult
+                & (RELAYOUT_RES_DRAG_RESIZING_DOCKED | RELAYOUT_RES_DRAG_RESIZING_FREEFORM)) != 0;
+        WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
+                requestedHeight, mTmpFrames.frame, dragResizing, mSurfaceSize);
+
         if (mAttachInfo.mContentCaptureManager != null) {
             MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
                     .getMainContentCaptureSession();
@@ -10701,4 +10747,15 @@
         }
         return mFallbackOnBackInvokedDispatcher;
     }
+
+    @Override
+    public void setTouchableRegion(Region r) {
+        if (r != null) {
+            mTouchableRegion = new Region(r);
+        } else {
+            mTouchableRegion = null;
+        }
+        mLastGivenInsets.reset();
+        requestLayout();
+    }
 }
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index e5c7d6d..27f89ec 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -36,6 +36,7 @@
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
 import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Log;
 
@@ -258,6 +259,7 @@
                 + " outFrame=" + outFrame.toShortString()
                 + " outParentFrame=" + outParentFrame.toShortString()
                 + " outDisplayFrame=" + outDisplayFrame.toShortString()
+                + " windowBounds=" + windowBounds.toShortString()
                 + " attachedWindowFrame=" + (attachedWindowFrame != null
                         ? attachedWindowFrame.toShortString()
                         : "null")
@@ -272,4 +274,45 @@
 
         return clippedByDisplayCutout;
     }
+
+    public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
+            int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
+            Point outSurfaceSize) {
+        int width;
+        int height;
+        if ((attrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
+            // For a scaled surface, we always want the requested size.
+            width = requestedWidth;
+            height = requestedHeight;
+        } else {
+            // When we're doing a drag-resizing, request a surface that's fullscreen size,
+            // so that we don't need to reallocate during the process. This also prevents
+            // buffer drops due to size mismatch.
+            if (dragResizing) {
+                // The maxBounds should match the display size which applies fixed-rotation
+                // transformation if there is any.
+                width = maxBounds.width();
+                height = maxBounds.height();
+            } else {
+                width = winFrame.width();
+                height = winFrame.height();
+            }
+        }
+
+        // This doesn't necessarily mean that there is an error in the system. The sizes might be
+        // incorrect, because it is before the first layout or draw.
+        if (width < 1) {
+            width = 1;
+        }
+        if (height < 1) {
+            height = 1;
+        }
+
+        // Adjust for surface insets.
+        final Rect surfaceInsets = attrs.surfaceInsets;
+        width += surfaceInsets.left + surfaceInsets.right;
+        height += surfaceInsets.top + surfaceInsets.bottom;
+
+        outSurfaceSize.set(width, height);
+    }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 998498b..3392edc 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.IBinder;
@@ -264,10 +263,10 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags,
             ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         final State state;
         synchronized (this) {
             state = mStateForWindow.get(window.asBinder());
@@ -286,7 +285,6 @@
         WindowManager.LayoutParams attrs = state.mParams;
 
         if (viewFlags == View.VISIBLE) {
-            outSurfaceSize.set(getSurfaceWidth(attrs), getSurfaceHeight(attrs));
             t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
             outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
         } else {
@@ -335,6 +333,11 @@
     }
 
     @Override
+    public void clearTouchableRegion(android.view.IWindow window) {
+        setTouchRegion(window.asBinder(), null);
+    }
+
+    @Override
     public void finishDrawing(android.view.IWindow window,
             android.view.SurfaceControl.Transaction postDrawTransaction) {
         synchronized (this) {
@@ -480,17 +483,6 @@
         return null;
     }
 
-    private int getSurfaceWidth(WindowManager.LayoutParams attrs) {
-      final Rect surfaceInsets = attrs.surfaceInsets;
-      return surfaceInsets != null
-          ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
-    }
-    private int getSurfaceHeight(WindowManager.LayoutParams attrs) {
-      final Rect surfaceInsets = attrs.surfaceInsets;
-      return surfaceInsets != null
-          ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
-    }
-
     @Override
     public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken,
                                          boolean grantFocus) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6a22023..849f3c9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
+import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
@@ -1793,6 +1795,14 @@
                 Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
                 return;
             }
+            if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
+                // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
+                // TODO (b/215533103): Introduce new modes in requestCursorUpdates().
+                // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
+                //  instead of mDisplayId.
+                mServedInputConnection.requestCursorUpdatesFromImm(
+                        CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, mDisplayId);
+            }
 
             try {
                 mService.startStylusHandwriting(mClient);
@@ -2419,9 +2429,9 @@
     public boolean isCursorAnchorInfoEnabled() {
         synchronized (mH) {
             final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+                    CURSOR_UPDATE_IMMEDIATE) != 0;
             final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+                    CURSOR_UPDATE_MONITOR) != 0;
             return isImmediate || isMonitoring;
         }
     }
@@ -2493,7 +2503,7 @@
             // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
             // not been changed from the previous call.
             final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+                    CURSOR_UPDATE_IMMEDIATE) != 0;
             if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
                 // TODO: Consider always emitting this message once we have addressed redundant
                 // calls of this method from android.widget.Editor.
@@ -2507,7 +2517,7 @@
             mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
             mCursorAnchorInfo = cursorAnchorInfo;
             // Clear immediate bit (if any).
-            mRequestUpdateCursorAnchorInfoMonitorMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
+            mRequestUpdateCursorAnchorInfoMonitorMode &= ~CURSOR_UPDATE_IMMEDIATE;
         }
     }
 
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 364ba50..f14c251 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,6 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 
@@ -135,6 +137,20 @@
     public @interface CacheMode {}
 
     /**
+     * Enable web content to apply light or dark style according to the app's theme
+     * and WebView to attempt to darken web content by algorithmic darkening when
+     * appropriate.
+     *
+     * Refer to {@link #setAllowAlgorithmicDarkening} for detail.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @SystemApi
+    public static final long ENABLE_SIMPLIFIED_DARK_MODE = 214741472L;
+
+    /**
      * Default cache usage mode. If the navigation type doesn't impose any
      * specific behavior, use cached resources when they are available
      * and not expired, otherwise load resources from the network.
@@ -239,6 +255,7 @@
      * automatically darkened.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_OFF = 0;
 
@@ -250,6 +267,7 @@
      * be inverted.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_AUTO = 1;
 
@@ -258,6 +276,7 @@
      * as to emulate a dark theme.
      *
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}
      */
     public static final int FORCE_DARK_ON = 2;
 
@@ -1533,6 +1552,13 @@
      *
      * @param forceDark the force dark mode to set.
      * @see #getForceDark
+     * @deprecated The "force dark" model previously implemented by WebView was complex
+     * and didn't interoperate well with current Web standards for
+     * prefers-color-scheme and color-scheme. In apps with
+     * {@code targetSdkVersion} &ge; {@link android.os.Build.VERSION_CODES#TIRAMISU}
+     * this API is a no-op and WebView will always use the dark style defined by web content
+     * authors if the app's theme is dark. To customize the behavior, refer to
+     * {@link #setAllowAlgorithmicDarkening}.
      */
     public void setForceDark(@ForceDark int forceDark) {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1544,6 +1570,7 @@
      *
      * @return the currently set force dark mode.
      * @see #setForceDark
+     * @deprecated refer to {@link #setForceDark}.
      */
     public @ForceDark int getForceDark() {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1551,6 +1578,49 @@
     }
 
     /**
+     * Control whether algorithmic darkening is allowed.
+     *
+     * <p class="note">
+     * <b>Note:</b> This API and the behaviour described only apply to apps with
+     * {@code targetSdkVersion} &ge; {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     *
+     * <p>
+     * WebView always sets the media query {@code prefers-color-scheme} according to the app's
+     * theme attribute {@link android.R.styleable#Theme_isLightTheme isLightTheme}, i.e.
+     * {@code prefers-color-scheme} is {@code light} if isLightTheme is true or not specified,
+     * otherwise it is {@code dark}. This means that the web content's light or dark style will
+     * be applied automatically to match the app's theme if the content supports it.
+     *
+     * <p>
+     * Algorithmic darkening is disallowed by default.
+     * <p>
+     * If the app's theme is dark and it allows algorithmic darkening, WebView will attempt to
+     * darken web content using an algorithm, if the content doesn't define its own dark styles
+     * and doesn't explicitly disable darkening.
+     *
+     * <p>
+     * If Android is applying Force Dark to WebView then WebView will ignore the value of
+     * this setting and behave as if it were set to true.
+     *
+     * @param allow allow algorithmic darkening or not.
+     */
+    public void setAllowAlgorithmicDarkening(boolean allow) {
+        // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+    }
+
+    /**
+     * Get if algorithmic darkening is allowed or not for this WebView.
+     * The default is false.
+     *
+     * @return if the algorithmic darkening is allowed or not.
+     * @see #setAllowAlgorithmicDarkening
+     */
+    public boolean getAllowAlgorithmicDarkening() {
+        // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
+        return false;
+    }
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = { "MENU_ITEM_" }, value = {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b21d08c..c6f64f4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1517,6 +1517,12 @@
         }
     }
 
+    /**
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
+     */
+    @Deprecated
     private final class ViewContentNavigation extends Action {
         final boolean mNext;
 
@@ -4121,7 +4127,11 @@
      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
      */
+    @Deprecated
     public void showNext(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, true /* next */));
     }
@@ -4130,7 +4140,11 @@
      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
      *
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
+     * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call
+     * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change
+     * unexpectedly.
      */
+    @Deprecated
     public void showPrevious(@IdRes int viewId) {
         addAction(new ViewContentNavigation(viewId, false /* next */));
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7161730..41c5401 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -350,6 +350,7 @@
  * @attr ref android.R.styleable#TextView_breakStrategy
  * @attr ref android.R.styleable#TextView_hyphenationFrequency
  * @attr ref android.R.styleable#TextView_lineBreakStyle
+ * @attr ref android.R.styleable#TextView_lineBreakWordStyle
  * @attr ref android.R.styleable#TextView_autoSizeTextType
  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
@@ -461,6 +462,13 @@
 
     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
 
+    // The default value of the line break style.
+    private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+
+    // The default value of the line break word style.
+    private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
+            LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+
     /**
      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
      * @hide
@@ -1453,6 +1461,11 @@
                             a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE));
                     break;
 
+                case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
+                    mLineBreakConfig.setLineBreakWordStyle(
+                            a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE));
+                    break;
+
                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
                     break;
@@ -3985,6 +3998,10 @@
         float mLetterSpacing = 0;
         String mFontFeatureSettings = null;
         String mFontVariationSettings = null;
+        boolean mHasLineBreakStyle = false;
+        boolean mHasLineBreakWordStyle = false;
+        int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
+        int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
 
         @Override
         public String toString() {
@@ -4015,6 +4032,10 @@
                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
+                    + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
+                    + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
+                    + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
+                    + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
                     + "}";
         }
     }
@@ -4062,6 +4083,10 @@
                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
+                com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
+                com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
     }
 
     /**
@@ -4177,6 +4202,16 @@
                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
                     attributes.mFontVariationSettings = appearance.getString(attr);
                     break;
+                case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
+                    attributes.mHasLineBreakStyle = true;
+                    attributes.mLineBreakStyle =
+                            appearance.getInt(attr, attributes.mLineBreakStyle);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
+                    attributes.mHasLineBreakWordStyle = true;
+                    attributes.mLineBreakWordStyle =
+                            appearance.getInt(attr, attributes.mLineBreakWordStyle);
+                    break;
                 default:
             }
         }
@@ -4242,9 +4277,46 @@
         if (attributes.mFontVariationSettings != null) {
             setFontVariationSettings(attributes.mFontVariationSettings);
         }
+
+        if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
+            updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
+                    attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
+                    attributes.mLineBreakWordStyle);
+        }
     }
 
     /**
+     * Updates the LineBreakConfig from the TextAppearance.
+     *
+     * This method updates the given line configuration from the TextAppearance. This method will
+     * request new layout if line break config has been changed.
+     *
+     * @param isLineBreakStyleSpecified true if the line break style is specified.
+     * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
+     * @param lineBreakStyle the value of the line break style in the TextAppearance.
+     * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
+     */
+    private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
+            boolean isLineBreakWordStyleSpecified,
+            @LineBreakConfig.LineBreakStyle int lineBreakStyle,
+            @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+        boolean updated = false;
+        if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) {
+            mLineBreakConfig.setLineBreakStyle(lineBreakStyle);
+            updated = true;
+        }
+        if (isLineBreakWordStyleSpecified
+                && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) {
+            mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle);
+            updated = true;
+        }
+        if (updated && mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+    /**
      * Get the default primary {@link Locale} of the text in this TextView. This will always be
      * the first member of {@link #getTextLocales()}.
      * @return the default primary {@link Locale} of the text in this TextView.
@@ -4800,18 +4872,29 @@
 
     /**
      * Sets line break configuration indicates which strategy needs to be used when calculating the
-     * text wrapping. There are thee strategies for the line break style(lb):
+     * text wrapping.
+     * <P>
+     * There are two types of line break rules that can be configured at the same time. One is
+     * line break style(lb) and the other is line break word style(lw). The line break style
+     * affects rule-based breaking. The line break word style affects dictionary-based breaking
+     * and provide phrase-based breaking opportunities. There are several types for the
+     * line break style:
      * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
      * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
      * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}.
-     * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE},
-     * which means no line break style is specified.
+     * The type for the line break word style is
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}.
+     * The default values of the line break style and the line break word style are
+     * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line
+     * breaking rules are specified.
      * See <a href="https://drafts.csswg.org/css-text/#line-break-property">
      *         the line-break property</a>
      *
      * @param lineBreakConfig the line break config for text wrapping.
      */
     public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
+        Objects.requireNonNull(lineBreakConfig);
         if (mLineBreakConfig.equals(lineBreakConfig)) {
             return;
         }
@@ -4858,7 +4941,13 @@
         mTextDir = params.getTextDirection();
         mBreakStrategy = params.getBreakStrategy();
         mHyphenationFrequency = params.getHyphenationFrequency();
-        mLineBreakConfig.set(params.getLineBreakConfig());
+        if (params.getLineBreakConfig() != null) {
+            mLineBreakConfig.set(params.getLineBreakConfig());
+        } else {
+            // Set default value if the line break config in the PrecomputedText.Params is null.
+            mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE);
+            mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE);
+        }
         if (mLayout != null) {
             nullLayouts();
             requestLayout();
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index cfccb71..fdaab66 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -17,6 +17,8 @@
 
 import static android.view.WindowManagerImpl.createWindowContextWindowManager;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -135,7 +137,8 @@
     }
 
     /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */
-    void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+    @VisibleForTesting(visibility = PACKAGE)
+    public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
         mCallbacksController.dispatchConfigurationChanged(newConfig);
     }
 
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 0470444..d1942ac 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -932,6 +932,24 @@
         });
     }
 
+    /**
+     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
+     *
+     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
+     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int)}
+     * @param imeDisplayId displayId on which IME is displayed.
+     */
+    @Dispatching(cancellable = true)
+    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int imeDisplayId) {
+        final int currentSessionId = mCurrentSessionId.get();
+        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
+            if (currentSessionId != mCurrentSessionId.get()) {
+                return;  // cancelled
+            }
+            requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
+        });
+    }
+
     @Dispatching(cancellable = true)
     @Override
     public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
@@ -940,24 +958,28 @@
             if (header.mSessionId != mCurrentSessionId.get()) {
                 return false;  // cancelled
             }
-            final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
-                Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
-                return false;
-            }
-            if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
-                // requestCursorUpdates() is not currently supported across displays.
-                return false;
-            }
-            try {
-                return ic.requestCursorUpdates(cursorUpdateMode);
-            } catch (AbstractMethodError ignored) {
-                // TODO(b/199934664): See if we can remove this by providing a default impl.
-                return false;
-            }
+            return requestCursorUpdatesInternal(cursorUpdateMode, imeDisplayId);
         });
     }
 
+    private boolean requestCursorUpdatesInternal(int cursorUpdateMode, int imeDisplayId) {
+        final InputConnection ic = getInputConnection();
+        if (ic == null || !isActive()) {
+            Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+            return false;
+        }
+        if (mParentInputMethodManager.getDisplayId() != imeDisplayId) {
+            // requestCursorUpdates() is not currently supported across displays.
+            return false;
+        }
+        try {
+            return ic.requestCursorUpdates(cursorUpdateMode);
+        } catch (AbstractMethodError ignored) {
+            // TODO(b/199934664): See if we can remove this by providing a default impl.
+            return false;
+        }
+    }
+
     @Dispatching(cancellable = true)
     @Override
     public void commitContent(InputConnectionCommandHeader header,
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 7f8accc..54e65e0 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -274,16 +274,15 @@
      * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}.
      *
      */
-    private static final int SUBSYSTEM_SHIFT = 32;
-    private static final long SUBSYSTEM_MASK = 0xF << SUBSYSTEM_SHIFT;
+    private static final long SUBSYSTEM_MASK = 0xF_0000_0000L;
     /**
      * Power constant not associated with a subsystem.
      */
-    public static final long SUBSYSTEM_NONE = 0 << SUBSYSTEM_SHIFT;
+    public static final long SUBSYSTEM_NONE = 0x0_0000_0000L;
     /**
      * Modem power constant.
      */
-    public static final long SUBSYSTEM_MODEM = 1 << SUBSYSTEM_SHIFT;
+    public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L;
 
     @LongDef(prefix = { "SUBSYSTEM_" }, value = {
             SUBSYSTEM_NONE,
@@ -292,7 +291,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Subsystem {}
 
-    private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFFFFFF;
+    private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
 
     /**
      * A map from Power Use Item to its power consumption.
@@ -582,7 +581,7 @@
         handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
                 POWER_MODEM_CONTROLLER_SLEEP, 0);
         handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
-                POWER_MODEM_CONTROLLER_SLEEP, 0);
+                POWER_MODEM_CONTROLLER_IDLE, 0);
         handleDeprecatedModemConstant(
                 ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX,
                 POWER_MODEM_CONTROLLER_RX, 0);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 8d1f16b..44c7f54 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -22,6 +22,7 @@
 import android.app.IActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
+import android.net.TrafficStats;
 import android.os.Build;
 import android.os.DeadObjectException;
 import android.os.IBinder;
@@ -32,7 +33,6 @@
 import android.util.Slog;
 
 import com.android.internal.logging.AndroidConfig;
-import com.android.server.NetworkManagementSocketTagger;
 
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
@@ -254,7 +254,7 @@
         /*
          * Wire socket tagging to traffic stats.
          */
-        NetworkManagementSocketTagger.install();
+        TrafficStats.attachSocketTagger();
 
         initialized = true;
     }
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index 456ff4b..afea69a 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -22,9 +22,9 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseDoubleArray;
 
-import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -64,34 +64,27 @@
      */
     private final SparseDoubleArray mPowerConstants = new SparseDoubleArray();
 
-    private static final int MODEM_DRAIN_TYPE_SHIFT = 28;
-    private static final int MODEM_DRAIN_TYPE_MASK = 0xF << MODEM_DRAIN_TYPE_SHIFT;
-
-    private static final int MODEM_TX_LEVEL_SHIFT = 24;
-    private static final int MODEM_TX_LEVEL_MASK = 0xF << MODEM_TX_LEVEL_SHIFT;
-
-    private static final int MODEM_RAT_TYPE_SHIFT = 20;
-    private static final int MODEM_RAT_TYPE_MASK = 0xF << MODEM_RAT_TYPE_SHIFT;
-
-    private static final int MODEM_NR_FREQUENCY_RANGE_SHIFT = 16;
-    private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0xF << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000;
+    private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000;
+    private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000;
+    private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000;
 
     /**
      * Corresponds to the overall modem battery drain while asleep.
      */
-    public static final int MODEM_DRAIN_TYPE_SLEEP = 0 << MODEM_DRAIN_TYPE_SHIFT;
+    public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000;
 
     /**
      * Corresponds to the overall modem battery drain while idle.
      */
-    public static final int MODEM_DRAIN_TYPE_IDLE = 1 << MODEM_DRAIN_TYPE_SHIFT;
+    public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000;
 
     /**
      * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain
      * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and
      * {@link ModemNrFrequencyRange} (when applicable).
      */
-    public static final int MODEM_DRAIN_TYPE_RX = 2 << MODEM_DRAIN_TYPE_SHIFT;
+    public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000;
 
     /**
      * Corresponds to the modem battery drain while receiving data.
@@ -99,7 +92,7 @@
      * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with
      * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable).
      */
-    public static final int MODEM_DRAIN_TYPE_TX = 3 << MODEM_DRAIN_TYPE_SHIFT;
+    public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000;
 
     @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = {
             MODEM_DRAIN_TYPE_SLEEP,
@@ -111,33 +104,44 @@
     public @interface ModemDrainType {
     }
 
-    private static final String[] MODEM_DRAIN_TYPE_NAMES =
-            new String[]{"SLEEP", "IDLE", "RX", "TX"};
+
+    private static final SparseArray<String> MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4);
+    static {
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX");
+    }
 
     /**
      * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}.
      */
-    public static final int MODEM_TX_LEVEL_0 = 0 << MODEM_TX_LEVEL_SHIFT;
+
+    public static final int MODEM_TX_LEVEL_0 = 0x0000_0000;
 
     /**
      * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}.
      */
-    public static final int MODEM_TX_LEVEL_1 = 1 << MODEM_TX_LEVEL_SHIFT;
+
+    public static final int MODEM_TX_LEVEL_1 = 0x0100_0000;
 
     /**
      * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}.
      */
-    public static final int MODEM_TX_LEVEL_2 = 2 << MODEM_TX_LEVEL_SHIFT;
+
+    public static final int MODEM_TX_LEVEL_2 = 0x0200_0000;
 
     /**
      * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}.
      */
-    public static final int MODEM_TX_LEVEL_3 = 3 << MODEM_TX_LEVEL_SHIFT;
+
+    public static final int MODEM_TX_LEVEL_3 = 0x0300_0000;
 
     /**
      * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}.
      */
-    public static final int MODEM_TX_LEVEL_4 = 4 << MODEM_TX_LEVEL_SHIFT;
+
+    public static final int MODEM_TX_LEVEL_4 = 0x0400_0000;
 
     private static final int MODEM_TX_LEVEL_COUNT = 5;
 
@@ -152,21 +156,37 @@
     public @interface ModemTxLevel {
     }
 
+    private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
+    static {
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
+        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+    }
+
+    private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
+            MODEM_TX_LEVEL_0,
+            MODEM_TX_LEVEL_1,
+            MODEM_TX_LEVEL_2,
+            MODEM_TX_LEVEL_3,
+            MODEM_TX_LEVEL_4};
+
     /**
      * Fallback for any active modem usage that does not match specified Radio Access Technology
      * (RAT) power constants.
      */
-    public static final int MODEM_RAT_TYPE_DEFAULT = 0 << MODEM_RAT_TYPE_SHIFT;
+    public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000;
 
     /**
      * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT.
      */
-    public static final int MODEM_RAT_TYPE_LTE = 1 << MODEM_RAT_TYPE_SHIFT;
+    public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000;
 
     /**
      * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT.
      */
-    public static final int MODEM_RAT_TYPE_NR = 2 << MODEM_RAT_TYPE_SHIFT;
+    public static final int MODEM_RAT_TYPE_NR = 0x0020_0000;
 
     @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = {
             MODEM_RAT_TYPE_DEFAULT,
@@ -177,33 +197,38 @@
     public @interface ModemRatType {
     }
 
-    private static final String[] MODEM_RAT_TYPE_NAMES = new String[]{"DEFAULT", "LTE", "NR"};
+    private static final SparseArray<String> MODEM_RAT_TYPE_NAMES = new SparseArray<>(3);
+    static {
+        MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT");
+        MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE");
+        MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR");
+    }
 
     /**
      * Fallback for any active 5G modem usage that does not match specified NR frequency power
      * constants.
      */
-    public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000;
 
     /**
      * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}.
      */
-    public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 1 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000;
 
     /**
      * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}.
      */
-    public static final int MODEM_NR_FREQUENCY_RANGE_MID = 2 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000;
 
     /**
      * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}.
      */
-    public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 3 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000;
 
     /**
      * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}.
      */
-    public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 4 << MODEM_NR_FREQUENCY_RANGE_SHIFT;
+    public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000;
 
     @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = {
             MODEM_RAT_TYPE_DEFAULT,
@@ -215,9 +240,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ModemNrFrequencyRange {
     }
-
-    private static final String[] MODEM_NR_FREQUENCY_RANGE_NAMES =
-            new String[]{"DEFAULT", "LOW", "MID", "HIGH", "MMWAVE"};
+    private static final SparseArray<String> MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5);
+    static {
+        MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT");
+        MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW");
+        MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID");
+        MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH");
+        MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE");
+    }
 
     public ModemPowerProfile() {
     }
@@ -261,11 +291,10 @@
         final int ratType;
         final int nrfType;
         try {
-            ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_SHIFT,
-                    MODEM_RAT_TYPE_NAMES);
+            ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES);
             if (ratType == MODEM_RAT_TYPE_NR) {
                 nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY,
-                        MODEM_NR_FREQUENCY_RANGE_SHIFT, MODEM_NR_FREQUENCY_RANGE_NAMES);
+                        MODEM_NR_FREQUENCY_RANGE_NAMES);
             } else {
                 nrfType = 0;
             }
@@ -299,10 +328,7 @@
                                         MODEM_TX_LEVEL_COUNT - 1));
                         continue;
                     }
-                    final int modemTxLevel = level << MODEM_TX_LEVEL_SHIFT;
-                    Slog.d("MWACHENS",
-                            "parsing tx at level:" + level + ", aka 0x" + Integer.toHexString(
-                                    modemTxLevel));
+                    final int modemTxLevel = MODEM_TX_LEVEL_MAP[level];
                     final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType;
                     setPowerConstant(txKey, txDrain);
                     break;
@@ -312,20 +338,31 @@
         }
     }
 
-    private static int getTypeFromAttribute(XmlResourceParser parser, String attr, int shift,
-            String[] names) {
+    private static int getTypeFromAttribute(XmlResourceParser parser, String attr,
+            SparseArray<String> names) {
         final String value = XmlUtils.readStringAttribute(parser, attr);
-        final int index = ArrayUtils.indexOf(names, value);
         if (value == null) {
             // Attribute was not specified, just use the default.
             return 0;
         }
+        int index = -1;
+        final int size = names.size();
+        // Manual linear search for string. (SparseArray uses == not equals.)
+        for (int i = 0; i < size; i++) {
+            if (value.equals(names.valueAt(i))) {
+                index = i;
+            }
+        }
         if (index < 0) {
+            final String[] stringNames = new String[size];
+            for (int i = 0; i < size; i++) {
+                stringNames[i] = names.valueAt(i);
+            }
             throw new IllegalArgumentException(
                     "Unexpected " + attr + " value : " + value + ". Acceptable values are "
-                            + Arrays.toString(names));
+                            + Arrays.toString(stringNames));
         }
-        return index << shift;
+        return names.keyAt(index);
     }
 
     /**
@@ -384,39 +421,35 @@
     private static String keyToString(int key) {
         StringBuilder sb = new StringBuilder();
         final int drainType = key & MODEM_DRAIN_TYPE_MASK;
-        appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES,
-                drainType >> MODEM_DRAIN_TYPE_SHIFT);
+        appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
         sb.append(",");
 
         if (drainType == MODEM_DRAIN_TYPE_TX) {
-            final int txLevel = (key & MODEM_TX_LEVEL_MASK) >> MODEM_TX_LEVEL_SHIFT;
-            sb.append("level:");
-            sb.append(txLevel);
-            sb.append(",");
+            final int txLevel = key & MODEM_TX_LEVEL_MASK;
+            appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
         }
 
         final int ratType = key & MODEM_RAT_TYPE_MASK;
-        appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType >> MODEM_RAT_TYPE_SHIFT);
+        appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType);
 
         if (ratType == MODEM_RAT_TYPE_NR) {
             sb.append(",");
             final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK;
-            appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES,
-                    nrFreq >> MODEM_NR_FREQUENCY_RANGE_SHIFT);
+            appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq);
         }
         return sb.toString();
     }
-
-    private static void appendFieldToString(StringBuilder sb, String fieldName, String[] names,
-            int index) {
+    private static void appendFieldToString(StringBuilder sb, String fieldName,
+            SparseArray<String> names, int key) {
         sb.append(fieldName);
         sb.append(":");
-        if (index < 0 || index >= names.length) {
-            sb.append("UNKNOWN(");
-            sb.append(index);
+        final String name = names.get(key, null);
+        if (name == null) {
+            sb.append("UNKNOWN(0x");
+            sb.append(Integer.toHexString(key));
             sb.append(")");
         } else {
-            sb.append(names[index]);
+            sb.append(name);
         }
     }
 
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index b06a7f4..b32a6b0 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -244,10 +244,12 @@
         writeContaminantPresenceStatus(dump, "contaminant_presence_status",
                 UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS,
                 status.getContaminantDetectionStatus());
-        dump.write("usb_data_enabled", UsbPortStatusProto.USB_DATA_ENABLED,
-                status.getUsbDataStatus());
+        dump.write("usb_data_status", UsbPortStatusProto.USB_DATA_STATUS,
+                UsbPort.usbDataStatusToString(status.getUsbDataStatus()));
         dump.write("is_power_transfer_limited", UsbPortStatusProto.IS_POWER_TRANSFER_LIMITED,
                 status.isPowerTransferLimited());
+        dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS,
+                UsbPort.powerBrickStatusToString(status.getPowerBrickStatus()));
         dump.end(token);
     }
 }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 998ec96..51e150e 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -249,7 +249,8 @@
 
     optional android.service.NetworkStatsServiceDumpProto netstats = 3001 [
         (section).type = SECTION_DUMPSYS,
-        (section).args = "netstats --proto"
+        (section).args = "netstats --proto",
+        (section).userdebug_and_eng_only = true
     ];
 
     optional android.providers.settings.SettingsServiceDumpProto settings = 3002 [
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 11560a5c..c33b7c9 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -69,7 +69,7 @@
     // know what activity types to check for when invoking splitscreen multi-window.
     optional bool is_home_recents_component = 6;
     repeated IdentifierProto pending_activities = 7 [deprecated=true];
-    optional int32 default_min_size_resizable_task = 8;
+    optional int32 default_min_size_resizable_task = 8 [deprecated=true];
 }
 
 message BarControllerProto {
@@ -226,7 +226,7 @@
     optional bool is_sleeping = 36;
     repeated string sleep_tokens = 37;
     repeated .android.graphics.RectProto keep_clear_areas = 38;
-
+    optional int32 min_size_of_resizeable_task_dp = 39;
 }
 
 /* represents DisplayArea object */
@@ -439,7 +439,7 @@
     optional bool is_on_screen = 37;
     optional bool is_visible = 38;
     optional bool pending_seamless_rotation = 39;
-    optional int64 finished_seamless_rotation_frame = 40;
+    optional int64 finished_seamless_rotation_frame = 40 [deprecated=true];
     optional WindowFramesProto window_frames = 41;
     optional bool force_seamless_rotation = 42;
     optional bool has_compat_scale = 43;
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index b3f54f9..c5eaf42 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -264,8 +264,9 @@
     optional DataRole data_role = 4;
     repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
     optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6;
-    optional bool usb_data_enabled = 7;
+    optional string usb_data_status = 7;
     optional bool is_power_transfer_limited = 8;
+    optional string usb_power_brick_status = 9;
 }
 
 message UsbPortStatusRoleCombinationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a4d4069..f106872 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -974,6 +974,61 @@
         android:permissionFlags="softRestricted|immutablyRestricted"
         android:protectionLevel="dangerous" />
 
+    <!-- Required to be able to read audio files from shared storage.
+     <p>Protection level: dangerous -->
+    <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
+                      android:icon="@drawable/perm_group_read_media_aural"
+                      android:label="@string/permgrouplab_readMediaAural"
+                      android:description="@string/permgroupdesc_readMediaAural"
+                      android:priority="950" />
+
+    <!-- Allows an application to read audio files from external storage.
+      <p>This permission is enforced starting in API level
+      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+     <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_AUDIO"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaAudio"
+                android:description="@string/permdesc_readMediaAudio"
+                android:protectionLevel="dangerous" />
+
+    <!-- Required to be able to read image and video files from shared storage.
+     <p>Protection level: dangerous -->
+    <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
+                      android:icon="@drawable/perm_group_read_media_visual"
+                      android:label="@string/permgrouplab_readMediaVisual"
+                      android:description="@string/permgroupdesc_readMediaVisual"
+                      android:priority="1000" />
+
+    <!-- Allows an application to read audio files from external storage.
+    <p>This permission is enforced starting in API level
+    {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+    For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+    targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+    must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+   <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_VIDEO"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaVideo"
+                android:description="@string/permdesc_readMediaVideo"
+                android:protectionLevel="dangerous" />
+
+    <!-- Allows an application to read image files from external storage.
+      <p>This permission is enforced starting in API level
+      {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+      For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+      must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+     <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_IMAGE"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readMediaImage"
+                android:description="@string/permdesc_readMediaImage"
+                android:protectionLevel="dangerous" />
+
     <!-- Allows an application to write to external storage.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
@@ -2306,6 +2361,26 @@
     <permission android:name="android.permission.MANAGE_FACTORY_RESET_PROTECTION"
         android:protectionLevel="signature|privileged"/>
 
+    <!-- ======================================== -->
+    <!-- Permissions for lost mode -->
+    <!-- ======================================== -->
+    <eat-comment />
+
+    <!-- @SystemApi Allows an application to trigger lost mode on an organization-owned device.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.TRIGGER_LOST_MODE"
+        android:protectionLevel="signature|role"/>
+
+    <!-- @SystemApi Allows an application to instruct the framework to send location to the device
+         admin when an organization-owned device is in lost mode.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES"
+        android:protectionLevel="signature|privileged"/>
+
     <!-- ================================== -->
     <!-- Permissions for accessing hardware -->
     <!-- ================================== -->
@@ -4251,7 +4326,8 @@
 
     <!-- Allows low-level access to setting the keyboard layout.
          <p>Not for use by third-party applications.
-         @hide -->
+         @hide
+         @TestApi -->
     <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
         android:protectionLevel="signature" />
 
@@ -6199,6 +6275,9 @@
          <p>Not for use by third-party applications.</p> -->
     <attribution android:tag="MusicRecognitionManagerService"
         android:label="@string/music_recognition_manager_service"/>
+    <!-- Attribution for Device Policy Manager service. -->
+    <attribution android:tag="DevicePolicyManagerService"
+        android:label="@string/device_policy_manager_service"/>
 
     <application android:process="system"
                  android:persistent="true"
@@ -6690,7 +6769,7 @@
         </service>
 
         <!-- TODO: Move to ExtServices or relevant component. -->
-        <service android:name="com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService"
+        <service android:name="android.service.selectiontoolbar.DefaultSelectionToolbarRenderService"
                  android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
                  android:process=":ui"
                  android:exported="false">
diff --git a/core/res/res/drawable/ic_ime_nav_back.xml b/core/res/res/drawable/ic_ime_nav_back.xml
new file mode 100644
index 0000000..ca329aa
--- /dev/null
+++ b/core/res/res/drawable/ic_ime_nav_back.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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="28dp"
+    android:height="28dp"
+    android:autoMirrored="true"
+    android:viewportWidth="28"
+    android:viewportHeight="28">
+    <path
+        android:pathData="M16.78,10.03l-3.97,3.97l3.97,3.97l-1.06,1.06l-5.03,-5.03l5.03,-5.03z"
+        android:fillColor="#FFFFFFFF" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_ime_switcher.xml b/core/res/res/drawable/ic_ime_switcher.xml
new file mode 100644
index 0000000..6c3b766
--- /dev/null
+++ b/core/res/res/drawable/ic_ime_switcher.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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="20dp"
+        android:height="20dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml
new file mode 100644
index 0000000..6fc9c69
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_aural.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 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="@android:color/white"
+        android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml
new file mode 100644
index 0000000..a5db271
--- /dev/null
+++ b/core/res/res/drawable/perm_group_read_media_visual.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 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="@android:color/white"
+        android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/input_method_nav_back.xml b/core/res/res/layout/input_method_nav_back.xml
new file mode 100644
index 0000000..671766a
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_back.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+
+<android.inputmethodservice.navigationbar.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/input_method_nav_back"
+    android:layout_width="@dimen/input_method_navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:scaleType="center"
+    android:contentDescription="@string/input_method_nav_back_button_desc"
+    android:paddingStart="@dimen/input_method_navigation_key_padding"
+    android:paddingEnd="@dimen/input_method_navigation_key_padding"
+    />
diff --git a/core/res/res/layout/input_method_nav_home_handle.xml b/core/res/res/layout/input_method_nav_home_handle.xml
new file mode 100644
index 0000000..501f512
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_home_handle.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<android.inputmethodservice.navigationbar.NavigationHandle
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/input_method_nav_home_handle"
+    android:layout_width="72dp"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:paddingStart="@dimen/input_method_navigation_key_padding"
+    android:paddingEnd="@dimen/input_method_navigation_key_padding"
+    />
diff --git a/core/res/res/layout/input_method_nav_ime_switcher.xml b/core/res/res/layout/input_method_nav_ime_switcher.xml
new file mode 100644
index 0000000..b571ba9
--- /dev/null
+++ b/core/res/res/layout/input_method_nav_ime_switcher.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<android.inputmethodservice.navigationbar.KeyButtonView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/input_method_nav_ime_switcher"
+    android:layout_width="@dimen/input_method_navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:contentDescription="@string/input_method_ime_switch_button_desc"
+    android:scaleType="center"
+    android:paddingStart="@dimen/input_method_navigation_key_padding"
+    android:paddingEnd="@dimen/input_method_navigation_key_padding"
+    />
diff --git a/core/res/res/layout/input_method_navigation_bar.xml b/core/res/res/layout/input_method_navigation_bar.xml
new file mode 100644
index 0000000..ce402fb
--- /dev/null
+++ b/core/res/res/layout/input_method_navigation_bar.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+<android.inputmethodservice.navigationbar.NavigationBarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/input_method_navigation_bar_view"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:clipChildren="false"
+    android:clipToPadding="false">
+
+    <android.inputmethodservice.navigationbar.NavigationBarInflaterView
+        android:id="@+id/input_method_nav_inflater"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false" />
+
+</android.inputmethodservice.navigationbar.NavigationBarView>
diff --git a/core/res/res/layout/input_method_navigation_layout.xml b/core/res/res/layout/input_method_navigation_layout.xml
new file mode 100644
index 0000000..05e750a
--- /dev/null
+++ b/core/res/res/layout/input_method_navigation_layout.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="@dimen/input_method_rounded_corner_content_padding"
+    android:layout_marginEnd="@dimen/input_method_rounded_corner_content_padding"
+    android:paddingStart="@dimen/input_method_nav_content_padding"
+    android:paddingEnd="@dimen/input_method_nav_content_padding"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:id="@+id/input_method_nav_horizontal">
+
+    <FrameLayout
+        android:id="@+id/input_method_nav_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false">
+
+        <LinearLayout
+            android:id="@+id/input_method_nav_ends_group"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:clipToPadding="false"
+            android:clipChildren="false" />
+
+        <LinearLayout
+            android:id="@+id/input_method_nav_center_group"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:orientation="horizontal"
+            android:clipToPadding="false"
+            android:clipChildren="false" />
+
+    </FrameLayout>
+
+</FrameLayout>
diff --git a/core/res/res/values-sw360dp/dimens.xml b/core/res/res/values-sw360dp/dimens.xml
index 4c74264..00de60e 100644
--- a/core/res/res/values-sw360dp/dimens.xml
+++ b/core/res/res/values-sw360dp/dimens.xml
@@ -18,4 +18,8 @@
 -->
 <resources>
     <dimen name="chooser_grid_padding">16dp</dimen>
+
+    <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME -->
+    <dimen name="input_method_navigation_key_width">80dip</dimen>
+
 </resources>
diff --git a/core/res/res/values-sw372dp/dimens.xml b/core/res/res/values-sw372dp/dimens.xml
new file mode 100644
index 0000000..cb29a19
--- /dev/null
+++ b/core/res/res/values-sw372dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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>
+    <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME -->
+    <dimen name="input_method_nav_content_padding">8dp</dimen>
+</resources>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index e8f15fd..4c70ea3 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -41,6 +41,11 @@
     <!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
     <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen>
 
+    <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME -->
+    <dimen name="input_method_navigation_key_width">128dp</dimen>
+    <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME -->
+    <dimen name="input_method_navigation_key_padding">25dp</dimen>
+
     <!-- Height of FaceUnlock view in keyguard -->
     <dimen name="face_unlock_height">430dip</dimen>
 
diff --git a/core/res/res/values-sw900dp/dimens.xml b/core/res/res/values-sw900dp/dimens.xml
index 11092b2..9ec4204 100644
--- a/core/res/res/values-sw900dp/dimens.xml
+++ b/core/res/res/values-sw900dp/dimens.xml
@@ -24,4 +24,12 @@
          the same as @dimen/navigation_bar_height -->
     <dimen name="navigation_bar_height_landscape">56dp</dimen>
 
+    <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. -->
+    <dimen name="input_method_navigation_key_width">80dp</dimen>
+    <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. -->
+    <dimen name="input_method_navigation_key_padding">0dp</dimen>
+    <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the
+         IME. -->
+    <dimen name="input_method_nav_key_button_ripple_max_width">76dp</dimen>
+
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a5da1ba..8696f5a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5038,6 +5038,10 @@
         <attr name="fontFeatureSettings" format="string" />
         <!-- Font variation settings. -->
         <attr name="fontVariationSettings" format="string"/>
+        <!-- Specifies the strictness of line-breaking rules applied within an element. -->
+        <attr name="lineBreakStyle" />
+        <!-- Specifies the phrase-based breaking opportunities. -->
+        <attr name="lineBreakWordStyle" />
     </declare-styleable>
     <declare-styleable name="TextClock">
         <!-- Specifies the formatting pattern used to show the time and/or date
@@ -5436,6 +5440,13 @@
             <!-- ndicates breaking text with the most strictest line-breaking rules. -->
             <enum name="strict" value="3" />
         </attr>
+        <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+        <attr name="lineBreakWordStyle">
+            <!-- No line break word style specific. -->
+            <enum name="none" value="0" />
+            <!-- Specify the phrase based breaking. -->
+            <enum name="phrase" value="1" />
+        </attr>
         <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
         works only for TextView. -->
         <attr name="autoSizeTextType" format="enum">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c0c8618..02d6293d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -134,9 +134,6 @@
          be sent during a change to the audio output device. -->
     <bool name="config_sendAudioBecomingNoisy">true</bool>
 
-    <!-- Whether Hearing Aid profile is supported -->
-    <bool name="config_hearing_aid_profile_supported">false</bool>
-
     <!-- Flag to disable all transition animations -->
     <bool name="config_disableTransitionAnimation">false</bool>
 
@@ -404,10 +401,6 @@
     <string-array translatable="false" name="config_tether_bluetooth_regexs">
     </string-array>
 
-    <!-- Max number of Bluetooth tethering connections allowed. If this is
-         updated config_tether_dhcp_range has to be updated appropriately. -->
-    <integer translatable="false" name="config_max_pan_devices">5</integer>
-
     <!-- This setting is deprecated, please use
          com.android.networkstack.tethering.R.array.config_dhcp_range instead. -->
     <string-array translatable="false" name="config_tether_dhcp_range">
@@ -1944,53 +1937,38 @@
     <!-- Integer to set a max latency the accelerometer will batch sensor requests with. -->
     <integer name="config_flipToScreenOffMaxLatencyMicros">2000000</integer>
 
-    <!-- Boolean indicating if current platform supports bluetooth SCO for off call
-    use cases -->
+    <!-- Note: This config is deprecated
+          Boolean indicating if current platform supports bluetooth SCO for off call
+          use cases
+    -->
     <bool name="config_bluetooth_sco_off_call">true</bool>
 
-    <!-- Boolean indicating if current platform supports bluetooth wide band
-         speech -->
-    <bool name="config_bluetooth_wide_band_speech">true</bool>
-
-    <!-- Boolean indicating if current platform need do one-time bluetooth address
-         re-validation -->
+    <!-- Note: This config is deprecated
+          Boolean indicating if current platform need do one-time bluetooth address
+          re-validation
+    -->
     <bool name="config_bluetooth_address_validation">false</bool>
 
-    <!-- Boolean indicating if current platform supports BLE peripheral mode -->
-    <bool name="config_bluetooth_le_peripheral_mode_supported">false</bool>
-
-    <!-- Boolean indicating if current platform supports HFP inband ringing -->
-    <bool name="config_bluetooth_hfp_inband_ringing_support">false</bool>
-
-    <!-- Max number of scan filters supported by blutooth controller. 0 if the
-         device does not support hardware scan filters-->
-    <integer translatable="false" name="config_bluetooth_max_scan_filters">0</integer>
-
-    <!-- Max number of advertisers supported by bluetooth controller. 0 if the
-         device does not support multiple advertisement-->
-    <integer translatable="false" name="config_bluetooth_max_advertisers">0</integer>
-
-    <!-- Idle current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Idle current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_idle_cur_ma">0</integer>
 
-    <!-- Rx current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Rx current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_rx_cur_ma">0</integer>
 
-    <!-- Tx current for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Tx current for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_tx_cur_ma">0</integer>
 
-    <!-- Operating volatage for bluetooth controller. 0 by default-->
+    <!-- Note: This config is deprecated, use BluetoothProperties instead.
+         Operating volatage for bluetooth controller. 0 by default
+    -->
     <integer translatable="false" name="config_bluetooth_operating_voltage_mv">0</integer>
 
-    <!-- Max number of connected audio devices supported by Bluetooth stack -->
-    <integer name="config_bluetooth_max_connected_audio_devices">5</integer>
-
-    <!-- Whether supported profiles should be reloaded upon enabling bluetooth -->
-    <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool>
-
-    <!-- Enabling autoconnect over pan -->
-    <bool name="config_bluetooth_pan_enable_autoconnect">false</bool>
-
     <!-- The default data-use polling period. -->
     <integer name="config_datause_polling_period_sec">600</integer>
 
@@ -2144,10 +2122,6 @@
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
 
-    <!-- Enable/disable default bluetooth profiles:
-        HSP_AG, ObexObjectPush, Audio, NAP -->
-    <bool name="config_bluetooth_default_profiles">true</bool>
-
     <!-- IP address of the dns server to use if nobody else suggests one -->
     <string name="config_default_dns_server" translatable="false">8.8.8.8</string>
 
@@ -4325,6 +4299,10 @@
          or empty if the default should be used. -->
     <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
 
+    <!-- Class name of the device specific implementation of DeviceStatePolicy.Provider
+        or empty if the default should be used. -->
+    <string translatable="false" name="config_deviceSpecificDeviceStatePolicyProvider"></string>
+
     <!-- Component name of media projection permission dialog -->
     <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 
@@ -4353,8 +4331,6 @@
     <!-- Component name that should be granted Notification Assistant access -->
     <string name="config_defaultAssistantAccessComponent" translatable="false">android.ext.services/android.ext.services.notification.Assistant</string>
 
-    <bool name="config_supportBluetoothPersistedState">true</bool>
-
     <bool name="config_keepRestrictedProfilesInBackground">true</bool>
 
     <!-- Cellular network service package name to bind to by default. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a877bd3..3f08e4b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -131,6 +131,19 @@
         corners. -->
     <dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen>
 
+    <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. -->
+    <dimen name="input_method_navigation_key_width">70dp</dimen>
+    <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. -->
+    <dimen name="input_method_navigation_key_padding">0dp</dimen>
+    <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME. -->
+    <dimen name="input_method_nav_content_padding">0px</dimen>
+    <!-- Copied from SysUI's @dimen/rounded_corner_content_padding for the embedded nav bar in the
+         IME. -->
+    <dimen name="input_method_rounded_corner_content_padding">0px</dimen>
+    <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the
+         IME. -->
+    <dimen name="input_method_nav_key_button_ripple_max_width">95dp</dimen>
+
     <!-- Width of the window of the divider bar used to resize docked stacks. -->
     <dimen name="docked_stack_divider_thickness">48dp</dimen>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 42386fc..505fe59 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3212,6 +3212,7 @@
 
   <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" />
   <public type="attr" name="lineBreakStyle" id="0x0101064d" />
+  <public type="attr" name="lineBreakWordStyle" id="0x0101064e" />
 
   <staging-public-group-final type="id" first-id="0x01fe0000">
     <public name="accessibilityActionDragStart" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 48b5a4f..59ad302 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -521,6 +521,8 @@
     <string name="twilight_service">Twilight Service</string>
     <!-- Attribution for Gnss Time Update service. [CHAR LIMIT=NONE]-->
     <string name="gnss_time_update_service">GNSS Time Update Service</string>
+    <!-- Attribution for Device Policy Manager service. [CHAR LIMIT=NONE]-->
+    <string name="device_policy_manager_service">Device Policy Manager Service</string>
 
     <!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
     <string name="music_recognition_manager_service">Music Recognition Manager Service</string>
@@ -876,6 +878,16 @@
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_storage">access photos, media, and files on your device</string>
 
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+    <string name="permgrouplab_readMediaAural">Music &amp; other audio</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+    <string name="permgroupdesc_readMediaAural">access audio files on your device</string>
+
+    <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]-->
+    <string name="permgrouplab_readMediaVisual">Photos &amp; videos</string>
+    <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+    <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string>
+
     <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgrouplab_microphone">Microphone</string>
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1889,6 +1901,21 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
     <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaAudio">read audio files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaVideo">read video files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readMediaImage">read image files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
     <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
@@ -3277,6 +3304,11 @@
     <!-- Title for EditText context menu [CHAR LIMIT=20] -->
     <string name="editTextMenuTitle">Text actions</string>
 
+    <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="input_method_nav_back_button_desc">Back</string>
+    <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="input_method_ime_switch_button_desc">Switch input method</string>
+
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f639350..4d8f9a2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -279,8 +279,6 @@
   <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
   <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
-  <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
-  <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
   <java-symbol type="bool" name="config_cellBroadcastAppLinks" />
   <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
   <java-symbol type="bool" name="config_disableTransitionAnimation" />
@@ -344,7 +342,6 @@
   <java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
   <java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
   <java-symbol type="bool" name="config_enableScreenshotChord" />
-  <java-symbol type="bool" name="config_bluetooth_default_profiles" />
   <java-symbol type="bool" name="config_enableWifiDisplay" />
   <java-symbol type="bool" name="config_allowAnimationsInLowPowerMode" />
   <java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
@@ -418,9 +415,6 @@
   <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" />
   <java-symbol type="integer" name="config_pictureInPictureMaxNumberOfActions" />
   <java-symbol type="dimen" name="config_closeToSquareDisplayMaxAspectRatio" />
-  <java-symbol type="integer" name="config_bluetooth_max_advertisers" />
-  <java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
-  <java-symbol type="integer" name="config_bluetooth_max_connected_audio_devices" />
   <java-symbol type="integer" name="config_burnInProtectionMinHorizontalOffset" />
   <java-symbol type="integer" name="config_burnInProtectionMaxHorizontalOffset" />
   <java-symbol type="integer" name="config_burnInProtectionMinVerticalOffset" />
@@ -430,9 +424,6 @@
   <java-symbol type="integer" name="config_bluetooth_rx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_tx_cur_ma" />
   <java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
-  <java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
-  <java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
-  <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
   <java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
@@ -451,7 +442,6 @@
   <java-symbol type="integer" name="config_wakeUpToLastStateTimeoutMillis" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAbsolute" />
-  <java-symbol type="integer" name="config_max_pan_devices" />
   <java-symbol type="integer" name="config_ntpPollingInterval" />
   <java-symbol type="integer" name="config_ntpPollingIntervalShorter" />
   <java-symbol type="integer" name="config_ntpRetry" />
@@ -2439,6 +2429,24 @@
   <!-- From PinyinIME(!!!) -->
   <java-symbol type="string" name="inputMethod" />
 
+  <!-- Gestural Nav buttons within InputMethodService -->
+  <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" />
+  <java-symbol type="drawable" name="ic_ime_nav_back" />
+  <java-symbol type="drawable" name="ic_ime_switcher" />
+  <java-symbol type="id" name="input_method_nav_back" />
+  <java-symbol type="id" name="input_method_nav_buttons" />
+  <java-symbol type="id" name="input_method_nav_center_group" />
+  <java-symbol type="id" name="input_method_nav_ends_group" />
+  <java-symbol type="id" name="input_method_nav_home_handle" />
+  <java-symbol type="id" name="input_method_nav_horizontal" />
+  <java-symbol type="id" name="input_method_nav_ime_switcher" />
+  <java-symbol type="id" name="input_method_nav_inflater" />
+  <java-symbol type="layout" name="input_method_navigation_bar" />
+  <java-symbol type="layout" name="input_method_navigation_layout" />
+  <java-symbol type="layout" name="input_method_nav_back" />
+  <java-symbol type="layout" name="input_method_nav_home_handle" />
+  <java-symbol type="layout" name="input_method_nav_ime_switcher" />
+
   <!-- From Chromium-WebView -->
   <java-symbol type="attr" name="actionModeWebSearchDrawable" />
   <java-symbol type="string" name="websearch" />
@@ -3824,8 +3832,6 @@
 
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
-  <java-symbol type="bool" name="config_supportBluetoothPersistedState" />
-
   <java-symbol type="string" name="slices_permission_request" />
 
   <java-symbol type="string" name="screenshot_edit" />
@@ -4649,4 +4655,6 @@
   <java-symbol type="bool" name="config_enableSafetyCenter" />
 
   <java-symbol type="string" name="config_deviceManagerUpdater" />
+
+  <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
 </resources>
diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml
new file mode 100644
index 0000000..2257114
--- /dev/null
+++ b/core/tests/coretests/res/xml/power_profile_test.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<device name="Android">
+    <!-- This is the battery capacity in mAh -->
+    <item name="battery.capacity">3000</item>
+
+    <!-- Number of cores each CPU cluster contains -->
+    <array name="cpu.clusters.cores">
+        <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) -->
+        <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) -->
+    </array>
+
+    <!-- Power consumption when CPU is suspended -->
+    <item name="cpu.suspend">5</item>
+    <!-- Additional power consumption when CPU is in a kernel idle loop -->
+    <item name="cpu.idle">1.11</item>
+    <!-- Additional power consumption by CPU excluding cluster and core when  running -->
+    <item name="cpu.active">2.55</item>
+
+    <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it -->
+    <item name="cpu.cluster_power.cluster0">2.11</item>
+    <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it -->
+    <item name="cpu.cluster_power.cluster1">2.22</item>
+
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster0">
+        <value>300000</value> <!-- 300 MHz CPU speed -->
+        <value>1000000</value> <!-- 1000 MHz CPU speed -->
+        <value>2000000</value> <!-- 2000 MHz CPU speed -->
+    </array>
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster1">
+        <value>300000</value> <!-- 300 MHz CPU speed -->
+        <value>1000000</value> <!-- 1000 MHz CPU speed -->
+        <value>2500000</value> <!-- 2500 MHz CPU speed -->
+        <value>3000000</value> <!-- 3000 MHz CPU speed -->
+    </array>
+
+    <!-- Additional power used by a CPU from cluster 0 when running at different
+         speeds. Currently this measurement also includes cluster cost. -->
+    <array name="cpu.core_power.cluster0">
+        <value>10</value> <!-- 300 MHz CPU speed -->
+        <value>20</value> <!-- 1000 MHz CPU speed -->
+        <value>30</value> <!-- 1900 MHz CPU speed -->
+    </array>
+    <!-- Additional power used by a CPU from cluster 1 when running at different
+         speeds. Currently this measurement also includes cluster cost. -->
+    <array name="cpu.core_power.cluster1">
+        <value>25</value> <!-- 300 MHz CPU speed -->
+        <value>35</value> <!-- 1000 MHz CPU speed -->
+        <value>50</value> <!-- 2500 MHz CPU speed -->
+        <value>60</value> <!-- 3000 MHz CPU speed -->
+    </array>
+
+    <!-- Power used by display unit in ambient display mode, including back lighting-->
+    <item name="ambient.on">0.5</item>
+    <!-- Additional power used when screen is turned on at minimum brightness -->
+    <item name="screen.on">100</item>
+    <!-- Additional power used when screen is at maximum brightness, compared to
+         screen at minimum brightness -->
+    <item name="screen.full">800</item>
+
+    <!-- Average power used by the camera flash module when on -->
+    <item name="camera.flashlight">500</item>
+    <!-- Average power use by the camera subsystem for a typical camera
+         application. Intended as a rough estimate for an application running a
+         preview and capturing approximately 10 full-resolution pictures per
+         minute. -->
+    <item name="camera.avg">600</item>
+
+    <!-- Additional power used by the audio hardware, probably due to DSP -->
+    <item name="audio">100.0</item>
+
+    <!-- Additional power used by the video hardware, probably due to DSP -->
+    <item name="video">150.0</item> <!-- ~50mA -->
+
+    <!-- Additional power used when GPS is acquiring a signal -->
+    <item name="gps.on">10</item>
+
+    <!-- Additional power used when cellular radio is transmitting/receiving -->
+    <item name="radio.active">60</item>
+    <!-- Additional power used when cellular radio is paging the tower -->
+    <item name="radio.scanning">3</item>
+    <!-- Additional power used when the cellular radio is on. Multi-value entry,
+         one per signal strength (no signal, weak, moderate, strong) -->
+    <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
+        <value>6</value>       <!-- none -->
+        <value>5</value>       <!-- poor -->
+        <value>4</value>       <!-- moderate -->
+        <value>3</value>       <!-- good -->
+        <value>3</value>       <!-- great -->
+    </array>
+
+    <!-- Cellular modem related values. These constants are deprecated, but still supported and
+         need to be tested -->
+    <item name="modem.controller.sleep">123</item>
+    <item name="modem.controller.idle">456</item>
+    <item name="modem.controller.rx">789</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+        <value>10</value>
+        <value>20</value>
+        <value>30</value>
+        <value>40</value>
+        <value>50</value>
+    </array>
+</device>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
index 09985a8..aa188f2 100644
--- a/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
+++ b/core/tests/coretests/src/android/content/ComponentCallbacksControllerTest.java
@@ -116,25 +116,4 @@
         @Override
         public void onLowMemory() {}
     }
-
-    private static class TestComponentCallbacks2 implements ComponentCallbacks2 {
-        private Configuration mConfiguration;
-        private boolean mLowMemoryCalled;
-        private int mLevel;
-
-        @Override
-        public void onConfigurationChanged(@NonNull Configuration newConfig) {
-            mConfiguration = newConfig;
-        }
-
-        @Override
-        public void onLowMemory() {
-            mLowMemoryCalled = true;
-        }
-
-        @Override
-        public void onTrimMemory(int level) {
-            mLevel = level;
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/content/ContextWrapperTest.java b/core/tests/coretests/src/android/content/ContextWrapperTest.java
new file mode 100644
index 0000000..ecaf1f4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContextWrapperTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 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.content;
+
+import static android.content.ContextWrapper.COMPONENT_CALLBACK_ON_WRAPPER;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.window.WindowContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+
+/**
+ *  Build/Install/Run:
+ *   atest FrameworksCoreTests:ContextWrapperTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContextWrapperTest {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    /**
+     * Before {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+     * register {@link ComponentCallbacks} to {@link ContextWrapper#getApplicationContext} before
+     * {@link ContextWrapper#attachBaseContext(Context)}.
+     */
+    @DisableCompatChanges(COMPONENT_CALLBACK_ON_WRAPPER)
+    @Test
+    public void testRegisterComponentCallbacksWithoutBaseContextBeforeT() {
+        final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+        final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+        // It should be no-op if unregister a ComponentCallbacks without registration.
+        wrapper.unregisterComponentCallbacks(callbacks);
+
+        wrapper.registerComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper.size()).isEqualTo(1);
+        assertThat(wrapper.mCallbacksRegisteredToSuper.get(0)).isEqualTo(callbacks);
+
+        wrapper.unregisterComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper.isEmpty()).isTrue();
+    }
+
+    /**
+     * After {@link android.os.Build.VERSION_CODES#TIRAMISU}, {@link ContextWrapper} must
+     * throw {@link IllegalStateException} before {@link ContextWrapper#attachBaseContext(Context)}.
+     */
+    @Test
+    public void testRegisterComponentCallbacksWithoutBaseContextAfterT() {
+        final ContextWrapper wrapper = new TestContextWrapper(null /* base */);
+        final ComponentCallbacks callbacks = new TestComponentCallbacks2();
+
+        try {
+            wrapper.unregisterComponentCallbacks(callbacks);
+            fail("ContextWrapper#unregisterComponentCallbacks must throw Exception before"
+                    + " ContextWrapper#attachToBaseContext.");
+        } catch (IllegalStateException ignored) {
+            // It is expected to throw IllegalStateException.
+        }
+
+        try {
+            wrapper.registerComponentCallbacks(callbacks);
+            fail("ContextWrapper#registerComponentCallbacks must throw Exception before"
+                    + " ContextWrapper#attachToBaseContext.");
+        } catch (IllegalStateException ignored) {
+            // It is expected to throw IllegalStateException.
+        }
+    }
+
+    /**
+     * {@link ContextWrapper#registerComponentCallbacks(ComponentCallbacks)} must delegate to its
+     * {@link ContextWrapper#getBaseContext()}, so does
+     * {@link ContextWrapper#unregisterComponentCallbacks(ComponentCallbacks)}.
+     */
+    @Test
+    public void testRegisterComponentCallbacks() {
+        final Context appContext = ApplicationProvider.getApplicationContext();
+        final Display display = appContext.getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        final WindowContext windowContext = (WindowContext) appContext.createWindowContext(display,
+                TYPE_APPLICATION_OVERLAY, null /* options */);
+        final ContextWrapper wrapper = new ContextWrapper(windowContext);
+        final TestComponentCallbacks2 callbacks = new TestComponentCallbacks2();
+
+        wrapper.registerComponentCallbacks(callbacks);
+
+        assertThat(wrapper.mCallbacksRegisteredToSuper).isNull();
+
+        final Configuration dispatchedConfig = new Configuration();
+        dispatchedConfig.fontScale = 1.2f;
+        dispatchedConfig.windowConfiguration.setWindowingMode(
+                WindowConfiguration.WINDOWING_MODE_FREEFORM);
+        dispatchedConfig.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+        windowContext.dispatchConfigurationChanged(dispatchedConfig);
+
+        assertThat(callbacks.mConfiguration).isEqualTo(dispatchedConfig);
+    }
+
+    private static class TestContextWrapper extends ContextWrapper {
+        TestContextWrapper(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            // The default implementation of ContextWrapper#getApplicationContext is to delegate
+            // to the base Context, and it leads to NPE if #registerComponentCallbacks is called
+            // directly before attach to base Context.
+            // We call to ApplicationProvider#getApplicationContext to prevent NPE because
+            // developers may have its implementation to prevent NPE without attaching base Context.
+            final Context baseContext = getBaseContext();
+            if (baseContext == null) {
+                return ApplicationProvider.getApplicationContext();
+            } else {
+                return super.getApplicationContext();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/content/TestComponentCallbacks2.java b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
new file mode 100644
index 0000000..6ae7fc4
--- /dev/null
+++ b/core/tests/coretests/src/android/content/TestComponentCallbacks2.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.content;
+
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+class TestComponentCallbacks2 implements ComponentCallbacks2 {
+    android.content.res.Configuration mConfiguration;
+    boolean mLowMemoryCalled;
+    int mLevel;
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        mConfiguration = newConfig;
+    }
+
+    @Override
+    public void onLowMemory() {
+        mLowMemoryCalled = true;
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        mLevel = level;
+    }
+}
diff --git a/core/tests/coretests/src/android/os/VibratorInfoTest.java b/core/tests/coretests/src/android/os/VibratorInfoTest.java
index d0e03a2..88766e2 100644
--- a/core/tests/coretests/src/android/os/VibratorInfoTest.java
+++ b/core/tests/coretests/src/android/os/VibratorInfoTest.java
@@ -25,7 +25,6 @@
 import android.hardware.vibrator.Braking;
 import android.hardware.vibrator.IVibrator;
 import android.platform.test.annotations.Presubmit;
-import android.util.Range;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,10 +42,10 @@
     private static final float[] TEST_AMPLITUDE_MAP = new float[]{
             /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
 
-    private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
-    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
+    private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
                     TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
 
     @Test
@@ -142,95 +141,109 @@
     }
 
     @Test
-    public void testGetFrequencyRangeHz_invalidFrequencyMappingReturnsNull() {
+    public void testGetFrequencyProfile_unsetProfileIsEmpty() {
+        assertTrue(
+                new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyProfile().isEmpty());
+    }
+
+    @Test
+    public void testFrequencyProfile_invalidValuesCreatesEmptyProfile() {
         // Invalid, contains NaN values or empty array.
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID).build().getFrequencyRangeHz());
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        Float.NaN, 50, 25, TEST_AMPLITUDE_MAP))
-                .build().getFrequencyRangeHz());
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        150, Float.NaN, 25, TEST_AMPLITUDE_MAP))
-                .build().getFrequencyRangeHz());
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        150, 50, Float.NaN, TEST_AMPLITUDE_MAP))
-                .build().getFrequencyRangeHz());
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(150, 50, 25, null))
-                .build().getFrequencyRangeHz());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                150, Float.NaN, 25, TEST_AMPLITUDE_MAP).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                150, 50, Float.NaN, TEST_AMPLITUDE_MAP).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(150, 50, 25, null).isEmpty());
+        // Invalid, contains zero or negative frequency values.
+        assertTrue(new VibratorInfo.FrequencyProfile(-1, 50, 25, TEST_AMPLITUDE_MAP).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(150, 0, 25, TEST_AMPLITUDE_MAP).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(150, 50, -2, TEST_AMPLITUDE_MAP).isEmpty());
+        // Invalid max amplitude entries.
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                150, 50, 50, new float[] { -1, 0, 1, 1, 0 }).isEmpty());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                150, 50, 50, new float[] { 0, 1, 2, 1, 0 }).isEmpty());
         // Invalid, minFrequency > resonantFrequency
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, null))
-                .build().getFrequencyRangeHz());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                /* resonantFrequencyHz= */ 150, /* minFrequencyHz= */ 250, 25, TEST_AMPLITUDE_MAP)
+                .isEmpty());
         // Invalid, maxFrequency < resonantFrequency by changing resolution.
-        assertNull(new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        150, 50, /* frequencyResolutionHz= */ 10, null))
-                .build().getFrequencyRangeHz());
+        assertTrue(new VibratorInfo.FrequencyProfile(
+                150, 50, /* frequencyResolutionHz= */ 10, TEST_AMPLITUDE_MAP).isEmpty());
     }
 
     @Test
-    public void testGetFrequencyRangeHz_resultRangeDerivedFromHalMapping() {
-        VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
-                        /* resonantFrequencyHz= */ 150,
-                        /* minFrequencyHz= */ 50,
-                        /* frequencyResolutionHz= */ 25,
-                        new float[]{
-                                /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
-                                /* 200Hz= */ 0.8f}))
-                .build();
-
-        assertEquals(Range.create(50f, 200f), info.getFrequencyRangeHz());
+    public void testGetFrequencyRangeHz_emptyProfileReturnsNull() {
+        assertNull(new VibratorInfo.FrequencyProfile(
+                Float.NaN, 50, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+        assertNull(new VibratorInfo.FrequencyProfile(
+                150, Float.NaN, 25, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+        assertNull(new VibratorInfo.FrequencyProfile(
+                150, 50, Float.NaN, TEST_AMPLITUDE_MAP).getFrequencyRangeHz());
+        assertNull(new VibratorInfo.FrequencyProfile(150, 50, 25, null).getFrequencyRangeHz());
     }
 
     @Test
-    public void testGetMaxAmplitude_emptyMappingReturnsAlwaysZero() {
-        VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID).build();
-        assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
-        assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
-        assertEquals(0f, info.getMaxAmplitude(200f), TEST_TOLERANCE);
+    public void testGetFrequencyRangeHz_validProfileReturnsMappedValues() {
+        VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile(
+                /* resonantFrequencyHz= */ 150,
+                /* minFrequencyHz= */ 50,
+                /* frequencyResolutionHz= */ 25,
+                /* maxAmplitudes= */ new float[]{
+                /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
+                /* 200Hz= */ 0.8f});
 
-        info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+        assertEquals(50f, profile.getFrequencyRangeHz().getLower(), TEST_TOLERANCE);
+        assertEquals(200f, profile.getFrequencyRangeHz().getUpper(), TEST_TOLERANCE);
+    }
+
+    @Test
+    public void testGetMaxAmplitude_emptyProfileReturnsAlwaysZero() {
+        VibratorInfo.FrequencyProfile profile = EMPTY_FREQUENCY_PROFILE;
+        assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
+
+        profile = new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 150,
                         /* minFrequencyHz= */ Float.NaN,
                         /* frequencyResolutionHz= */ Float.NaN,
-                        null))
-                .build();
+                        /* maxAmplitudes= */ null);
 
-        assertEquals(0f, info.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
-        assertEquals(0f, info.getMaxAmplitude(100f), TEST_TOLERANCE);
-        assertEquals(0f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(Float.NaN), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(100f), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
     }
 
     @Test
-    public void testGetMaxAmplitude_validMappingReturnsMappedValues() {
-        VibratorInfo info = new VibratorInfo.Builder(TEST_VIBRATOR_ID)
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+    public void testGetMaxAmplitude_validProfileReturnsMappedValues() {
+        VibratorInfo.FrequencyProfile profile = new VibratorInfo.FrequencyProfile(
                         /* resonantFrequencyHz= */ 150,
                         /* minFrequencyHz= */ 50,
                         /* frequencyResolutionHz= */ 25,
-                        new float[]{
+                        /* maxAmplitudes= */ new float[]{
                                 /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f,
-                                /* 200Hz= */ 0.8f}))
-                .build();
+                                /* 200Hz= */ 0.8f});
 
-        assertEquals(1f, info.getMaxAmplitude(150f), TEST_TOLERANCE);
-        assertEquals(0.9f, info.getMaxAmplitude(175f), TEST_TOLERANCE);
-        assertEquals(0.8f, info.getMaxAmplitude(125f), TEST_TOLERANCE);
-        assertEquals(0.8f, info.getMaxAmplitude(info.getFrequencyRangeHz().getUpper()),
-                TEST_TOLERANCE); // 200Hz
-        assertEquals(0.1f, info.getMaxAmplitude(info.getFrequencyRangeHz().getLower()),
-                TEST_TOLERANCE); // 50Hz
+        // Values in the max amplitudes array should return exact measurement.
+        assertEquals(1f, profile.getMaxAmplitude(150f), TEST_TOLERANCE);
+        assertEquals(0.9f, profile.getMaxAmplitude(175f), TEST_TOLERANCE);
+        assertEquals(0.8f, profile.getMaxAmplitude(125f), TEST_TOLERANCE);
 
-        // 145Hz maps to the max amplitude for 125Hz, which is lower.
-        assertEquals(0.8f, info.getMaxAmplitude(145f), TEST_TOLERANCE); // 145Hz
-        // 185Hz maps to the max amplitude for 200Hz, which is lower.
-        assertEquals(0.8f, info.getMaxAmplitude(185f), TEST_TOLERANCE); // 185Hz
+        // Min and max frequencies should return exact measurement from array.
+        assertEquals(0.8f, profile.getMaxAmplitude(200f), TEST_TOLERANCE);
+        assertEquals(0.1f, profile.getMaxAmplitude(50f), TEST_TOLERANCE);
+
+        // Values outside [50Hz, 200Hz] just return 0.
+        assertEquals(0f, profile.getMaxAmplitude(49f), TEST_TOLERANCE);
+        assertEquals(0f, profile.getMaxAmplitude(201f), TEST_TOLERANCE);
+
+        // 145Hz maps to linear value between 125Hz and 150Hz max amplitudes 0.8 and 1.
+        assertEquals(0.96f, profile.getMaxAmplitude(145f), TEST_TOLERANCE);
+        // 185Hz maps to linear value between 175Hz and 200Hz max amplitudes 0.9 and 0.8.
+        assertEquals(0.86f, profile.getMaxAmplitude(185f), TEST_TOLERANCE);
     }
 
     @Test
@@ -245,7 +258,7 @@
                 .setPwlePrimitiveDurationMax(50)
                 .setPwleSizeMax(20)
                 .setQFactor(2f)
-                .setFrequencyMapping(TEST_FREQUENCY_MAPPING);
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE);
         VibratorInfo complete = completeBuilder.build();
 
         assertEquals(complete, complete);
@@ -272,23 +285,21 @@
                 .build();
         assertNotEquals(complete, completeWithDifferentPrimitiveDuration);
 
-        VibratorInfo completeWithDifferentFrequencyMapping = completeBuilder
-                .setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+        VibratorInfo completeWithDifferentFrequencyProfile = completeBuilder
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                         TEST_RESONANT_FREQUENCY + 20,
                         TEST_MIN_FREQUENCY + 10,
                         TEST_FREQUENCY_RESOLUTION + 5,
                         TEST_AMPLITUDE_MAP))
                 .build();
-        assertNotEquals(complete, completeWithDifferentFrequencyMapping);
+        assertNotEquals(complete, completeWithDifferentFrequencyProfile);
 
-        VibratorInfo completeWithEmptyFrequencyMapping = completeBuilder
-                .setFrequencyMapping(EMPTY_FREQUENCY_MAPPING)
+        VibratorInfo completeWithEmptyFrequencyProfile = completeBuilder
+                .setFrequencyProfile(EMPTY_FREQUENCY_PROFILE)
                 .build();
-        assertNotEquals(complete, completeWithEmptyFrequencyMapping);
+        assertNotEquals(complete, completeWithEmptyFrequencyProfile);
 
-        VibratorInfo completeWithUnknownQFactor = completeBuilder
-                .setQFactor(Float.NaN)
-                .build();
+        VibratorInfo completeWithUnknownQFactor = completeBuilder.setQFactor(Float.NaN).build();
         assertNotEquals(complete, completeWithUnknownQFactor);
 
         VibratorInfo completeWithDifferentQFactor = completeBuilder
@@ -316,7 +327,7 @@
                 .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                 .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
                 .setQFactor(Float.NaN)
-                .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
                 .build();
 
         Parcel parcel = Parcel.obtain();
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 981086d..7a66bef 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -61,6 +61,8 @@
     @Rule
     public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
+    private static final float TEST_TOLERANCE = 1e-5f;
+
     private Context mContextSpy;
     private Vibrator mVibratorSpy;
 
@@ -76,6 +78,9 @@
     @Test
     public void getId_returnsDefaultId() {
         assertEquals(-1, mVibratorSpy.getId());
+        assertEquals(-1, new SystemVibrator.NoVibratorInfo().getId());
+        assertEquals(-1, new SystemVibrator.MultiVibratorInfo(new VibratorInfo[] {
+                VibratorInfo.EMPTY_VIBRATOR_INFO, VibratorInfo.EMPTY_VIBRATOR_INFO }).getId());
     }
 
     @Test
@@ -90,8 +95,7 @@
 
     @Test
     public void areEffectsSupported_noVibrator_returnsAlwaysNo() {
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
-                new VibratorInfo[0]);
+        VibratorInfo info = new SystemVibrator.NoVibratorInfo();
         assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
                 info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
     }
@@ -104,7 +108,7 @@
         VibratorInfo unsupportedVibrator = new VibratorInfo.Builder(/* id= */ 2)
                 .setSupportedEffects(new int[0])
                 .build();
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
         assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO,
                 info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -116,7 +120,7 @@
                 .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                 .build();
         VibratorInfo unknownSupportVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{supportedVibrator, unknownSupportVibrator});
         assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN,
                 info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -130,7 +134,7 @@
         VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
                 .setSupportedEffects(VibrationEffect.EFFECT_CLICK)
                 .build();
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, secondVibrator});
         assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES,
                 info.isEffectSupported(VibrationEffect.EFFECT_CLICK));
@@ -148,8 +152,7 @@
 
     @Test
     public void arePrimitivesSupported_noVibrator_returnsAlwaysFalse() {
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
-                new VibratorInfo[0]);
+        VibratorInfo info = new SystemVibrator.NoVibratorInfo();
         assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
 
@@ -160,7 +163,7 @@
                 .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
                 .build();
         VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
         assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
@@ -175,7 +178,7 @@
                 .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                 .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 15)
                 .build();
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, secondVibrator});
         assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
@@ -192,8 +195,7 @@
 
     @Test
     public void getPrimitivesDurations_noVibrator_returnsAlwaysZero() {
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
-                new VibratorInfo[0]);
+        VibratorInfo info = new SystemVibrator.NoVibratorInfo();
         assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
 
@@ -204,7 +206,7 @@
                 .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10)
                 .build();
         VibratorInfo unsupportedVibrator = VibratorInfo.EMPTY_VIBRATOR_INFO;
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{supportedVibrator, unsupportedVibrator});
         assertEquals(0, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
@@ -219,12 +221,180 @@
                 .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS)
                 .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 20)
                 .build();
-        SystemVibrator.AllVibratorsInfo info = new SystemVibrator.AllVibratorsInfo(
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
                 new VibratorInfo[]{firstVibrator, secondVibrator});
         assertEquals(20, info.getPrimitiveDuration(VibrationEffect.Composition.PRIMITIVE_CLICK));
     }
 
     @Test
+    public void getQFactorAndResonantFrequency_noVibrator_returnsNaN() {
+        VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+        assertTrue(Float.isNaN(info.getQFactor()));
+        assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+    }
+
+    @Test
+    public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setQFactor(1f)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+                .build();
+        VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setQFactor(2f)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, secondVibrator});
+
+        assertTrue(Float.isNaN(info.getQFactor()));
+        assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+
+        // One vibrator with values undefined.
+        VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, thirdVibrator});
+
+        assertTrue(Float.isNaN(info.getQFactor()));
+        assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+    }
+
+    @Test
+    public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setQFactor(10f)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+                        /* resonantFrequencyHz= */ 11, 10, 0.5f, null))
+                .build();
+        VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setQFactor(10f)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(
+                        /* resonantFrequencyHz= */ 11, 5, 1, null))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, secondVibrator});
+
+        assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
+        assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+    }
+
+    @Test
+    public void getFrequencyProfile_noVibrator_returnsEmpty() {
+        VibratorInfo info = new SystemVibrator.NoVibratorInfo();
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+    }
+
+    @Test
+    public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+                        new float[] { 0, 1 }))
+                .build();
+        VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
+                        new float[] { 0, 1 }))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, differentResonantFrequency});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+
+        VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
+                        new float[] { 0, 1 }))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+    }
+
+    @Test
+    public void getFrequencyProfile_missingValues_returnsEmpty() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
+                        new float[] { 0, 1 }))
+                .build();
+        VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
+                        new float[] { 0, 1 }))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, missingResonantFrequency});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+
+        VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
+                        new float[] { 0, 1 }))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, missingMinFrequency});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+
+        VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
+                        new float[] { 0, 1 }))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+
+        VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
+                .build();
+        info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+    }
+
+    @Test
+    public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+                        new float[] { 0, 1, 1, 0 }))
+                .build();
+        VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
+                        new float[] { 0, 1, 1, 0 }))
+                .build();
+        VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+                        new float[] { 0, 1, 1, 0 }))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
+
+        assertTrue(info.getFrequencyProfile().isEmpty());
+    }
+
+    @Test
+    public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
+        VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
+                        new float[] { 0.5f, 1, 1, 0.5f }))
+                .build();
+        VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+                        new float[] { 1, 1, 1 }))
+                .build();
+        VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+                .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+                        new float[] { 0.8f, 1, 0.8f, 0.5f }))
+                .build();
+        VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
+                new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+        assertEquals(
+                new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+                info.getFrequencyProfile());
+    }
+
+    @Test
     public void vibrate_withVibrationAttributes_usesGivenAttributes() {
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
         VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index a5261ae..4319b97 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -19,6 +19,10 @@
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.DisplayCutout.extractBoundsFromList;
 import static android.view.DisplayCutout.fromSpec;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
@@ -497,6 +501,74 @@
                 new ParcelableWrapper(mCutoutNumbers));
     }
 
+    @Test
+    public void testGetRotatedBounds_top_rot0() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_0);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot90() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(100, 20, 0, 20),
+                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_90));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_90);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot180() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+                ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_180);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot270() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(0, 20, 100, 20),
+                ZERO_RECT, ZERO_RECT, new Rect(displayH - 100, 50, displayH - 0, 75), ZERO_RECT,
+                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_270));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
+                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
+                Insets.of(20, 0, 20, 0));
+        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_270);
+        assertEquals(expected, rotated);
+    }
+
+    @Test
+    public void testGetRotatedBounds_top_rot90to180() {
+        int displayW = 500, displayH = 1000;
+        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
+                ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
+                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
+        DisplayCutout cutout = new DisplayCutout(Insets.of(100, 20, 0, 20),
+                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
+                Insets.of(0, 20, 0, 20));
+        // starting from 90, so the start displayW/H are swapped:
+        DisplayCutout rotated = cutout.getRotated(displayH, displayW, ROTATION_90, ROTATION_180);
+        assertEquals(expected, rotated);
+    }
+
     private static DisplayCutout createCutoutTop() {
         return createCutoutWithInsets(0, 100, 0, 0);
     }
@@ -533,4 +605,11 @@
                 ZERO_RECT,
                 waterfallInsets);
     }
+
+    private static DisplayCutout.CutoutPathParserInfo createParserInfo(
+            @Surface.Rotation int rotation) {
+        return new DisplayCutout.CutoutPathParserInfo(
+                0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+                rotation, 0f /* scale */);
+    }
 }
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 656e756..b2a4044 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -21,16 +21,25 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.app.EmptyActivity;
 import android.app.Instrumentation;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.IBinder;
@@ -43,6 +52,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerImpl;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -79,6 +89,8 @@
     private final WindowContext mWindowContext = createWindowContext();
     private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
 
+    private static final int TIMEOUT_IN_SECONDS = 4;
+
     @Test
     public void testCreateWindowContextWindowManagerAttachClientToken() {
         final WindowManager windowContextWm = WindowManagerImpl
@@ -131,7 +143,7 @@
         });
 
 
-        assertTrue(latch.await(4, TimeUnit.SECONDS));
+        assertTrue(latch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
 
 
         // Verify that the window token of the window context is created after first addView().
@@ -234,7 +246,7 @@
         // Add the parent window
         mInstrumentation.runOnMainSync(() -> wm.addView(parentWindow, params));
 
-        assertTrue(listener.mLatch.await(4, TimeUnit.SECONDS));
+        assertTrue(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS));
 
         final WindowManager.LayoutParams subWindowAttrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_ATTACHED_DIALOG);
@@ -251,6 +263,63 @@
                 null /* theme */));
     }
 
+    @Test
+    public void testRegisterComponentCallbacks() {
+        final WindowContext windowContext = createWindowContext();
+        final ConfigurationListener listener = new ConfigurationListener();
+
+        windowContext.registerComponentCallbacks(listener);
+
+        try {
+            final Configuration config = new Configuration();
+            config.windowConfiguration.setWindowingMode(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM);
+            config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+            windowContext.dispatchConfigurationChanged(config);
+
+            try {
+                assertWithMessage("Waiting for onConfigurationChanged timeout.")
+                        .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                fail("Waiting for configuration changed failed because of " + e);
+            }
+
+            assertThat(listener.mConfiguration).isEqualTo(config);
+        } finally {
+            windowContext.unregisterComponentCallbacks(listener);
+        }
+    }
+
+    @Test
+    public void testRegisterComponentCallbacksOnWindowContextWrapper() {
+        final WindowContext windowContext = createWindowContext();
+        final Context wrapper = new ContextWrapper(windowContext);
+        final ConfigurationListener listener = new ConfigurationListener();
+
+        wrapper.registerComponentCallbacks(listener);
+
+        try {
+            final Configuration config = new Configuration();
+            config.windowConfiguration.setWindowingMode(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM);
+            config.windowConfiguration.setBounds(new Rect(0, 0, 100, 100));
+
+            windowContext.dispatchConfigurationChanged(config);
+
+            try {
+                assertWithMessage("Waiting for onConfigurationChanged timeout.")
+                        .that(listener.mLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                fail("Waiting for configuration changed failed because of " + e);
+            }
+
+            assertThat(listener.mConfiguration).isEqualTo(config);
+        } finally {
+            wrapper.unregisterComponentCallbacks(listener);
+        }
+    }
+
     private WindowContext createWindowContext() {
         return createWindowContext(TYPE_APPLICATION_OVERLAY);
     }
@@ -262,6 +331,20 @@
         return (WindowContext) instContext.createWindowContext(display, type,  null /* options */);
     }
 
+    private static class ConfigurationListener implements ComponentCallbacks {
+        private Configuration mConfiguration;
+        private CountDownLatch mLatch = new CountDownLatch(1);
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            mConfiguration = newConfig;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onLowMemory() {}
+    }
+
     private static class AttachStateListener implements View.OnAttachStateChangeListener {
         final CountDownLatch mLatch = new CountDownLatch(1);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 1efd78b..bc3b422 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -57,11 +57,13 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getContext();
-        mProfile = new PowerProfile(mContext, true);
+        mProfile = new PowerProfile(mContext);
     }
 
     @Test
     public void testPowerProfile() {
+        mProfile.forceInitForTesting(mContext, R.xml.power_profile_test);
+
         assertEquals(2, mProfile.getNumCpuClusters());
         assertEquals(4, mProfile.getNumCoresInCpuCluster(0));
         assertEquals(4, mProfile.getNumCoresInCpuCluster(1));
@@ -83,6 +85,43 @@
                 mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0));
         assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO));
         assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO));
+
+        assertEquals(123.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP));
+        assertEquals(456.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE));
+        assertEquals(789.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX));
+        assertEquals(10.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 0));
+        assertEquals(20.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 1));
+        assertEquals(30.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 2));
+        assertEquals(40.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 3));
+        assertEquals(50.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 4));
+
+        // Deprecated Modem constants should work with current format.
+        assertEquals(123.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP));
+        assertEquals(456.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE));
+        assertEquals(789.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(10.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(20.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(30.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(40.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(50.0, mProfile.getAverageBatteryDrainMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
     }
 
     @Test
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 92fca36..88920c8 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -231,6 +231,18 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_AUDIO" />
+    </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_VIDEO" />
+    </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="33">
+        <new-permission name="android.permission.READ_MEDIA_IMAGE" />
+    </split-permission>
     <split-permission name="android.permission.BLUETOOTH"
                       targetSdk="31">
         <new-permission name="android.permission.BLUETOOTH_SCAN" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index de086df..0e06fac 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -528,6 +528,7 @@
         <!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
         <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
+        <permission name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0752329..1567233 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -319,6 +319,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "-1764792832": {
+      "message": "Start collecting in Transition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "-1750384749": {
       "message": "Launch on display check: allow launch on public display",
       "level": "DEBUG",
@@ -349,6 +355,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayRotation.java"
     },
+    "-1728919185": {
+      "message": "        unrelated invisible sibling %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-1715268616": {
       "message": "Last window, removing starting window %s",
       "level": "VERBOSE",
@@ -445,12 +457,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "-1587921395": {
-      "message": "  Top targets: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-1585311008": {
       "message": "Bring to front target: %s from %s",
       "level": "DEBUG",
@@ -703,12 +709,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "-1375751630": {
-      "message": "  --- Start combine pass ---",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-1364754753": {
       "message": "Task vanished taskId=%d",
       "level": "VERBOSE",
@@ -1171,12 +1171,6 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
-    "-855366859": {
-      "message": "        merging children in from %s: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-853404763": {
       "message": "\twallpaper=%s",
       "level": "DEBUG",
@@ -1237,6 +1231,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-779095785": {
+      "message": "        sibling is a participant with mode %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "-775004869": {
       "message": "Not a match: %s",
       "level": "DEBUG",
@@ -1549,12 +1549,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
     },
-    "-446752714": {
-      "message": "        SKIP: sibling contains top target %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-445944810": {
       "message": "finish(%b): mCanceled=%b",
       "level": "DEBUG",
@@ -1723,12 +1717,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
-    "-302335479": {
-      "message": "        remove from topTargets %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "-292790591": {
       "message": "Attempted to set IME policy to a display that does not exist: %d",
       "level": "WARN",
@@ -2071,6 +2059,12 @@
       "group": "WM_DEBUG_STARTING_WINDOW",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "112145970": {
+      "message": "      SKIP: its sibling was rejected",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "114070759": {
       "message": "New wallpaper target: %s prevTarget: %s caller=%s",
       "level": "VERBOSE",
@@ -2407,6 +2401,12 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "405146734": {
+      "message": "  Final targets: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "416924848": {
       "message": "InsetsSource Control %s for target %s",
       "level": "DEBUG",
@@ -2431,12 +2431,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "430260320": {
-      "message": "        sibling is a top target with mode %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "431715812": {
       "message": "Launch on display check: allow launch any on display",
       "level": "DEBUG",
@@ -2755,12 +2749,6 @@
       "group": "WM_SHOW_SURFACE_ALLOC",
       "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
     },
-    "751854538": {
-      "message": "DisplayArea keep clear rects changed name =%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_ORGANIZER",
-      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
-    },
     "765395228": {
       "message": "onAnimationFinished(): controller=%s reorderMode=%d",
       "level": "DEBUG",
@@ -2791,6 +2779,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "800698875": {
+      "message": "SyncGroup %d: Started when there is other active SyncGroup",
+      "level": "WARN",
+      "group": "WM_DEBUG_SYNC_ENGINE",
+      "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+    },
     "806891543": {
       "message": "Setting mOrientationChangeComplete=true because wtoken %s numInteresting=%d numDrawn=%d",
       "level": "INFO",
@@ -3085,12 +3079,6 @@
       "group": "WM_DEBUG_WALLPAPER",
       "at": "com\/android\/server\/wm\/WallpaperController.java"
     },
-    "1186730970": {
-      "message": "          no common mode yet, so set it",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/Transition.java"
-    },
     "1191587912": {
       "message": "Moved rootTask=%s behind rootTask=%s",
       "level": "DEBUG",
@@ -3205,6 +3193,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1333520287": {
+      "message": "Creating PendingTransition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/TransitionController.java"
+    },
     "1337596507": {
       "message": "Sending to proc %s new compat %s",
       "level": "VERBOSE",
@@ -3823,6 +3817,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "2034988903": {
+      "message": "PendingStartTransaction found",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "2039056415": {
       "message": "Found matching affinity candidate!",
       "level": "DEBUG",
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 4d81858..cffdf28 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,7 +17,7 @@
 package android.graphics.text;
 
 import android.annotation.IntDef;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -58,7 +58,28 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
 
+    /**
+     * No line break word style specified.
+     */
+    public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
+
+    /**
+     * Indicates the line breaking is based on the phrased. This makes text wrapping only on
+     * meaningful words. The support of the text wrapping word style varies depending on the
+     * locales. If the locale does not support the phrase based text wrapping,
+     * there will be no effect.
+     */
+    public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
+        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LineBreakWordStyle {}
+
     private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE;
+    private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE;
 
     public LineBreakConfig() {
     }
@@ -66,14 +87,12 @@
     /**
      * Set the line break configuration.
      *
-     * @param config the new line break configuration.
+     * @param lineBreakConfig the new line break configuration.
      */
-    public void set(@Nullable LineBreakConfig config) {
-        if (config != null) {
-            mLineBreakStyle = config.getLineBreakStyle();
-        } else {
-            mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
-        }
+    public void set(@NonNull LineBreakConfig lineBreakConfig) {
+        Objects.requireNonNull(lineBreakConfig);
+        mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
+        mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
     }
 
     /**
@@ -94,17 +113,36 @@
         mLineBreakStyle = lineBreakStyle;
     }
 
+    /**
+     * Get the line break word style.
+     *
+     * @return The current line break word style to be used for the text wrapping.
+     */
+    public @LineBreakWordStyle int getLineBreakWordStyle() {
+        return mLineBreakWordStyle;
+    }
+
+    /**
+     * Set the line break word style.
+     *
+     * @param lineBreakWordStyle the new line break word style.
+     */
+    public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
+        mLineBreakWordStyle = lineBreakWordStyle;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == null) return false;
         if (this == o) return true;
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
-        return mLineBreakStyle == that.mLineBreakStyle;
+        return (mLineBreakStyle == that.mLineBreakStyle)
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLineBreakStyle);
+        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
     }
 }
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 5f4afb7..6d691c1 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -264,8 +264,10 @@
             Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
             int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
                     LineBreakConfig.LINE_BREAK_STYLE_NONE;
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end,
-                    isRtl);
+            int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() :
+                    LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+                    mCurrentOffset, end, isRtl);
             mCurrentOffset = end;
             return this;
         }
@@ -445,7 +447,8 @@
          *
          * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
          * @param paintPtr The native paint pointer to be applied.
-         * @param lineBreakStyle The line break style of the text.
+         * @param lineBreakStyle The line break style(lb) of the text.
+         * @param lineBreakWordStyle The line break word style(lw) of the text.
          * @param start The start offset in the copied buffer.
          * @param end The end offset in the copied buffer.
          * @param isRtl True if the text is RTL.
@@ -453,6 +456,7 @@
         private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
                                                 /* Non Zero */ long paintPtr,
                                                 int lineBreakStyle,
+                                                int lineBreakWordStyle,
                                                 @IntRange(from = 0) int start,
                                                 @IntRange(from = 0) int end,
                                                 boolean isRtl);
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml
new file mode 100644
index 0000000..723963f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="@android:color/system_accent1_100"/>
+    <corners android:radius="12dp"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml
new file mode 100644
index 0000000..0a3a813
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_background_ripple.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<!-- DO NOT SUBMIT - find right color!! -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@android:color/system_accent1_10">
+    <item android:drawable="@drawable/letterbox_education_dismiss_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
new file mode 100644
index 0000000..ff57406
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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">
+    <path
+        android:fillColor="@android:color/system_neutral2_400"
+        android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
new file mode 100644
index 0000000..16dea48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_expand_more_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@android:color/system_neutral2_200">
+    <item android:drawable="@drawable/letterbox_education_ic_expand_more"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
new file mode 100644
index 0000000..cbfcfd0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_reposition.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M 14 22 H 30 V 26 H 14 V 22 Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M26 16L34 24L26 32V16Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
new file mode 100644
index 0000000..469eb1e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_screen_rotation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M22.56 2H26C37.02 2 46 10.98 46 22H42C42 14.44 36.74 8.1 29.7 6.42L31.74 10L28.26 12L22.56 2ZM22 46H25.44L19.74 36L16.26 38L18.3 41.58C11.26 39.9 6 33.56 6 26H2C2 37.02 10.98 46 22 46ZM20.46 12L36 27.52L27.54 36L12 20.48L20.46 12ZM17.64 9.18C18.42 8.4 19.44 8 20.46 8C21.5 8 22.52 8.4 23.3 9.16L38.84 24.7C40.4 26.26 40.4 28.78 38.84 30.34L30.36 38.82C29.58 39.6 28.56 40 27.54 40C26.52 40 25.5 39.6 24.72 38.82L9.18 23.28C7.62 21.72 7.62 19.2 9.18 17.64L17.64 9.18Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
new file mode 100644
index 0000000..dcb8aed
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_ic_split_screen.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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="@dimen/letterbox_education_dialog_icon_size"
+        android:height="@dimen/letterbox_education_dialog_icon_size"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:fillType="evenOdd"
+        android:pathData="M2 8C0.895431 8 0 8.89543 0 10V38C0 39.1046 0.895431 40 2 40H46C47.1046 40 48 39.1046 48 38V10C48 8.89543 47.1046 8 46 8H2ZM44 12H4V36H44V12Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M6 16C6 14.8954 6.89543 14 8 14H21C22.1046 14 23 14.8954 23 16V32C23 33.1046 22.1046 34 21 34H8C6.89543 34 6 33.1046 6 32V16Z" />
+    <path
+        android:fillColor="@color/letterbox_education_text_secondary"
+        android:pathData="M25 16C25 14.8954 25.8954 14 27 14H40C41.1046 14 42 14.8954 42 16V32C42 33.1046 41.1046 34 40 34H27C25.8954 34 25 33.1046 25 32V16Z" />
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
new file mode 100644
index 0000000..cd1d99a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_action_layout.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/letterbox_education_dialog_action_width"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/letterbox_education_dialog_action_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginBottom="12dp"/>
+
+    <TextView
+        android:id="@+id/letterbox_education_dialog_action_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:lineSpacingExtra="4sp"
+        android:textAlignment="center"
+        android:textColor="@color/compat_controls_text"
+        android:textSize="14sp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
new file mode 100644
index 0000000..edf737f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -0,0 +1,98 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<com.android.wm.shell.compatui.LetterboxEduDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@color/compat_controls_background"
+    android:gravity="center"
+    android:paddingTop="24dp"
+    android:paddingBottom="32dp"
+    android:paddingHorizontal="32dp">
+
+    <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+    <LinearLayout
+        android:id="@+id/letterbox_education_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/letterbox_education_icon"
+            android:layout_width="@dimen/letterbox_education_dialog_icon_size"
+            android:layout_height="@dimen/letterbox_education_dialog_icon_size"
+            android:layout_marginBottom="20dp" />
+
+        <TextView
+            android:id="@+id/letterbox_education_dialog_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="@dimen/letterbox_education_dialog_title_max_width"
+            android:lineSpacingExtra="4sp"
+            android:textAlignment="center"
+            android:textColor="@color/compat_controls_text"
+            android:textSize="24sp"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="top"
+            android:orientation="horizontal"
+            android:paddingTop="43dp">
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_screen_rotation_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:icon="@drawable/letterbox_education_ic_screen_rotation"/>
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_split_screen_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+                app:icon="@drawable/letterbox_education_ic_split_screen"
+                app:text="@string/letterbox_education_split_screen_text"/>
+
+            <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout
+                android:id="@+id/letterbox_education_dialog_reposition_action"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                android:layout_marginStart="@dimen/letterbox_education_dialog_space_between_actions"
+                app:icon="@drawable/letterbox_education_ic_reposition"
+                app:text="@string/letterbox_education_reposition_text"/>
+
+        </LinearLayout>
+
+        <Button
+            android:id="@+id/letterbox_education_dialog_dismiss"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            android:layout_marginTop="43dp"
+            android:layout_marginHorizontal="24dp"
+            android:background="@drawable/letterbox_education_dismiss_background_ripple"
+            android:gravity="center"
+            android:text="@string/letterbox_education_got_it"
+            android:textColor="@android:color/system_neutral1_900"
+            android:textAlignment="center"
+            android:contentDescription="@string/letterbox_education_got_it"/>
+
+    </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduDialogLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml
new file mode 100644
index 0000000..f4c6d65
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_overlay_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:alpha="0"
+    android:background="@android:color/system_neutral1_900"
+    android:clickable="true">
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
new file mode 100644
index 0000000..a309d48
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_toast_layout.xml
@@ -0,0 +1,61 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<com.android.wm.shell.compatui.LetterboxEduToastLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@color/compat_controls_background"
+    android:gravity="center"
+    android:paddingVertical="14dp"
+    android:paddingHorizontal="16dp">
+
+    <!-- Adding an extra layer to animate the alpha of the background and content separately. -->
+    <LinearLayout
+        android:id="@+id/letterbox_education_content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/letterbox_education_icon"
+            android:layout_width="@dimen/letterbox_education_toast_icon_size"
+            android:layout_height="@dimen/letterbox_education_toast_icon_size"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="@dimen/letterbox_education_toast_text_max_width"
+            android:paddingHorizontal="16dp"
+            android:lineSpacingExtra="5sp"
+            android:text="@string/letterbox_education_toast_title"
+            android:textAlignment="viewStart"
+            android:textColor="@color/compat_controls_text"
+            android:textSize="16sp"
+            android:maxLines="1"
+            android:ellipsize="end"/>
+
+        <ImageButton
+            android:id="@+id/letterbox_education_toast_expand"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/letterbox_education_ic_expand_more_ripple"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/letterbox_education_expand_button_description"/>
+
+    </LinearLayout>
+
+</com.android.wm.shell.compatui.LetterboxEduToastLayout>
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
new file mode 100644
index 0000000..4aaeef8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2022 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>
+    <declare-styleable name="LetterboxEduDialogActionLayout">
+        <attr name="icon" format="reference" />
+        <attr name="text" format="string" />
+    </declare-styleable>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cf596f7..84aec64 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -34,6 +34,9 @@
     <color name="compat_controls_background">@android:color/system_neutral1_800</color>
     <color name="compat_controls_text">@android:color/system_neutral1_50</color>
 
+    <!-- Letterbox Education -->
+    <color name="letterbox_education_text_secondary">@android:color/system_neutral2_200</color>
+
     <!-- GM2 colors -->
     <color name="GM2_grey_200">#E8EAED</color>
     <color name="GM2_grey_700">#5F6368</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 1c19a10..ab2c9b1 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -219,6 +219,37 @@
     <!-- The width of the camera compat hint. -->
     <dimen name="camera_compat_hint_width">143dp</dimen>
 
+    <!-- The corner radius of the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_corner_radius">100dp</dimen>
+
+    <!-- The corner radius of the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_corner_radius">28dp</dimen>
+
+    <!-- The margin between the letterbox education toast/dialog and the bottom of the task. -->
+    <dimen name="letterbox_education_margin_bottom">16dp</dimen>
+
+    <!-- The size of the icon in the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_icon_size">24dp</dimen>
+
+    <!-- The size of an icon in the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_icon_size">48dp</dimen>
+
+    <!-- The width of each action container in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_action_width">136dp</dimen>
+
+    <!-- The space between two actions in the letterbox education dialog -->
+    <dimen name="letterbox_education_dialog_space_between_actions">18dp</dimen>
+
+    <!-- The maximum width of the title and subtitle in the letterbox education dialog. -->
+    <dimen name="letterbox_education_dialog_title_max_width">444dp</dimen>
+
+    <!-- The maximum width of the text in the letterbox education toast. -->
+    <dimen name="letterbox_education_toast_text_max_width">398dp</dimen>
+
+    <!-- The distance that the letterbox education dialog will move up during appear/dismiss
+         animation.  -->
+    <dimen name="letterbox_education_dialog_animation_elevation">20dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index ab0013a..a8a9ed7 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -171,4 +171,28 @@
          compatibility control. [CHAR LIMIT=NONE] -->
     <string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
 
+    <!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_dialog_title">Get the most out of <xliff:g id="app_name" example="YouTube">%s</xliff:g></string>
+
+    <!-- The title of the letterbox education toast. [CHAR LIMIT=60] -->
+    <string name="letterbox_education_toast_title">Rotate your device for a full-screen view</string>
+
+    <!-- Description of the rotate screen into portrait action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_screen_rotation_portrait_text">Rotate your screen to portrait</string>
+
+    <!-- Description of the rotate screen into landscape action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_screen_rotation_landscape_text">Rotate your screen to landscape</string>
+
+    <!-- Description of the put app in split-screen action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_split_screen_text">Drag in another app to use split screen</string>
+
+    <!-- Description of the reposition app action. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_reposition_text">Double tap to reposition</string>
+
+    <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
+    <string name="letterbox_education_got_it">Got it</string>
+
+    <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_education_expand_button_description">Expand for more information.</string>
+
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
new file mode 100644
index 0000000..762a037
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Custom layout for Letterbox Education dialog action.
+ */
+// TODO(b/215316431): Add tests
+class LetterboxEduDialogActionLayout extends FrameLayout {
+    private final ImageView mIcon;
+    private final TextView mText;
+
+    LetterboxEduDialogActionLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray styledAttributes =
+                context.getTheme().obtainStyledAttributes(
+                        attrs,
+                        R.styleable.LetterboxEduDialogActionLayout,
+                        /* defStyleAttr= */ 0,
+                        /* defStyleRes= */ 0);
+        int iconId = styledAttributes.getResourceId(
+                R.styleable.LetterboxEduDialogActionLayout_icon, 0);
+        String optionalText = styledAttributes.getString(
+                R.styleable.LetterboxEduDialogActionLayout_text);
+        styledAttributes.recycle();
+
+        View rootView = inflate(getContext(), R.layout.letterbox_education_dialog_action_layout,
+                this);
+
+        mIcon = rootView.findViewById(R.id.letterbox_education_dialog_action_icon);
+        mIcon.setImageResource(iconId);
+        mText = rootView.findViewById(R.id.letterbox_education_dialog_action_text);
+        if (optionalText != null) {
+            mText.setText(optionalText);
+        }
+    }
+
+    void setText(@StringRes int id) {
+        mText.setText(getResources().getString(id));
+    }
+
+    void setIconRotation(float rotation) {
+        mIcon.setRotation(rotation);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
new file mode 100644
index 0000000..662862a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for Letterbox Education Dialog.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduDialogLayout extends FrameLayout {
+
+    public LetterboxEduDialogLayout(Context context) {
+        this(context, null);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LetterboxEduDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Register a callback for the dismiss button.
+     * @param callback The callback to register
+     */
+    void setDismissOnClickListener(Runnable callback) {
+        findViewById(R.id.letterbox_education_dialog_dismiss).setOnClickListener(
+                view -> callback.run());
+    }
+
+    /**
+     * Updates the layout with the given app info.
+     * @param appIcon The name of the app
+     * @param appIcon The icon of the app
+     */
+    void updateAppInfo(String appName, Drawable appIcon) {
+        ((ImageView) findViewById(R.id.letterbox_education_icon)).setImageDrawable(appIcon);
+        ((TextView) findViewById(R.id.letterbox_education_dialog_title)).setText(
+                getResources().getString(R.string.letterbox_education_dialog_title, appName));
+    }
+
+    /**
+     * Updates the layout according to the given orientation.
+     * @param orientation The orientation of the display
+     */
+    void updateDisplayOrientation(@Orientation int orientation) {
+        boolean isOrientationPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+        ((LetterboxEduDialogActionLayout) findViewById(
+                R.id.letterbox_education_dialog_screen_rotation_action)).setText(
+                isOrientationPortrait
+                        ? R.string.letterbox_education_screen_rotation_landscape_text
+                        : R.string.letterbox_education_screen_rotation_portrait_text);
+
+        if (isOrientationPortrait) {
+            ((LetterboxEduDialogActionLayout) findViewById(
+                    R.id.letterbox_education_dialog_split_screen_action)).setIconRotation(90f);
+        }
+
+        findViewById(R.id.letterbox_education_dialog_reposition_action).setVisibility(
+                isOrientationPortrait ? View.GONE : View.VISIBLE);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
new file mode 100644
index 0000000..e7f592d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduToastLayout.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterboxedu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for the Letterbox Education Toast.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduToastLayout extends FrameLayout {
+
+    public LetterboxEduToastLayout(Context context) {
+        this(context, null);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public LetterboxEduToastLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    /**
+     * Register a callback for the dismiss button.
+     * @param callback The callback to register
+     */
+    void setExpandOnClickListener(Runnable callback) {
+        findViewById(R.id.letterbox_education_toast_expand).setOnClickListener(
+                view -> callback.run());
+    }
+
+    /**
+     * Updates the layout with the given app info.
+     * @param appName The name of the app
+     * @param appIcon The icon of the app
+     */
+    void updateAppInfo(String appName, Drawable appIcon) {
+        ImageView icon = findViewById(R.id.letterbox_education_icon);
+        icon.setContentDescription(appName);
+        icon.setImageDrawable(appIcon);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 4ecc0b6..1bef552e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -126,9 +126,6 @@
      */
     private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
 
-    //tmp vars for unused relayout params
-    private static final Point TMP_SURFACE_SIZE = new Point();
-
     private final Window mWindow;
     private final Runnable mClearWindowHandler;
     private final ShellExecutor mSplashScreenExecutor;
@@ -244,9 +241,9 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
-                    tmpControls, TMP_SURFACE_SIZE);
+                    tmpControls);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 68b0b4e..cb478c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -118,10 +118,10 @@
 fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
-        Region(0, 0, displayBounds.bounds.right,
+        Region.from(0, 0, displayBounds.bounds.right,
             dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
     } else {
-        Region(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+        Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.bottom)
     }
 }
@@ -129,10 +129,10 @@
 fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
     val displayBounds = WindowUtils.getDisplayBounds(rotation)
     return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
-        Region(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+        Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
             displayBounds.bounds.right, displayBounds.bounds.bottom)
     } else {
-        Region(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
+        Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
             displayBounds.bounds.right, displayBounds.bounds.bottom)
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index ae92366..b7c80df 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -24,13 +24,11 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import org.junit.After
-import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -84,11 +82,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index f1b0135..1ac664e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -24,12 +24,10 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,11 +67,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
index 6998cd2..65eb9aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
@@ -24,13 +24,11 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
 import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
 import org.junit.After
-import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -88,11 +86,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 7a53224..12910dd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -25,12 +25,10 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -73,11 +71,7 @@
 
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index efae207..cf4ea46 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -28,14 +28,14 @@
     component: FlickerComponentName
 ) : BaseAppHelper(instrumentation, activityLabel, component) {
     fun getPrimaryBounds(dividerBounds: Region): Region {
-        val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right,
+        val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right,
                 dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
         return primaryAppBounds
     }
 
     fun getSecondaryBounds(dividerBounds: Region): Region {
         val displayBounds = WindowUtils.displayBounds
-        val secondaryAppBounds = Region(0,
+        val secondaryAppBounds = Region.from(0,
                 dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
                 displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
         return secondaryAppBounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index 264d482..a510d69 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -166,9 +166,9 @@
             val dividerBounds =
                 layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
 
-            val topAppBounds = Region(0, 0, dividerBounds.right,
+            val topAppBounds = Region.from(0, 0, dividerBounds.right,
                 dividerBounds.top + WindowUtils.dockedStackDividerInset)
-            val bottomAppBounds = Region(0,
+            val bottomAppBounds = Region.from(0,
                 dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
                 displayBounds.right,
                 displayBounds.bottom - WindowUtils.navigationBarHeight)
@@ -187,9 +187,9 @@
             val dividerBounds =
                 layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
 
-            val topAppBounds = Region(0, 0, dividerBounds.right,
+            val topAppBounds = Region.from(0, 0, dividerBounds.right,
                 dividerBounds.top + WindowUtils.dockedStackDividerInset)
-            val bottomAppBounds = Region(0,
+            val bottomAppBounds = Region.from(0,
                 dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
                 displayBounds.right,
                 displayBounds.bottom - WindowUtils.navigationBarHeight)
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 09539ec..76ea2d5 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -65,11 +65,13 @@
 
 // Regular JNI
 static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
-                         jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) {
+                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
+                         jboolean isRtl) {
     Paint* paint = toPaint(paintPtr);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
-    toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl);
+    toBuilder(builderPtr)
+            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
 }
 
 // Regular JNI
@@ -144,7 +146,7 @@
 static const JNINativeMethod gMTBuilderMethods[] = {
         // MeasuredParagraphBuilder native functions.
         {"nInitBuilder", "()J", (void*)nInitBuilder},
-        {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun},
+        {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
         {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
         {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
         {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 553b08f..7c57bd5 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -78,7 +78,9 @@
 
 static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
                                                 const shaders::LinearEffect& linearEffect,
-                                                float maxDisplayLuminance, float maxLuminance) {
+                                                float maxDisplayLuminance,
+                                                float currentDisplayLuminanceNits,
+                                                float maxLuminance) {
     auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
     auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
     if (!runtimeEffect) {
@@ -89,8 +91,8 @@
 
     effectBuilder.child("child") = std::move(shader);
 
-    const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, mat4(),
-                                                             maxDisplayLuminance, maxLuminance);
+    const auto uniforms = shaders::buildLinearEffectUniforms(
+            linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
 
     for (const auto& uniform : uniforms) {
         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
@@ -201,7 +203,9 @@
             auto shader = layerImage->makeShader(sampling,
                                                  SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
             constexpr float kMaxDisplayBrightess = 1000.f;
+            constexpr float kCurrentDisplayBrightness = 500.f;
             shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
+                                              kCurrentDisplayBrightness,
                                               layer->getMaxLuminanceNits());
             paint.setShader(shader);
             canvas->drawRect(skiaDestRect, paint);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d862377..f627a3c 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -117,7 +117,7 @@
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
     size_t preferredFrameTimelineIndex =
             AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(cbData);
-    int64_t vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+    AVsyncId vsyncId = AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
             cbData, preferredFrameTimelineIndex);
     int64_t frameDeadline = AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
             cbData, preferredFrameTimelineIndex);
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index bf2e764..f656ebf 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -27,6 +27,7 @@
     name: "libservices",
     srcs: [
         ":IDropBoxManagerService.aidl",
+        ":ILogcatManagerService_aidl",
         "src/content/ComponentName.cpp",
         "src/os/DropBoxManager.cpp",
     ],
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index d6e203c..587222a 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -29,7 +29,6 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -180,13 +179,9 @@
     private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
     private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
-            + "LocationManager} methods to provide the provider explicitly.")
-    @Nullable private String mProvider;
+    private @Nullable String mProvider;
     private @Quality int mQuality;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "Use {@link "
-            + "LocationRequest} instead.")
-    private long mInterval;
+    private long mIntervalMillis;
     private long mMinUpdateIntervalMillis;
     private long mExpireAtRealtimeMillis;
     private long mDurationMillis;
@@ -195,7 +190,7 @@
     private final long mMaxUpdateDelayMillis;
     private boolean mHideFromAppOps;
     private final boolean mAdasGnssBypass;
-    private boolean mLocationSettingsIgnored;
+    private boolean mBypass;
     private boolean mLowPower;
     private @Nullable WorkSource mWorkSource;
 
@@ -208,9 +203,7 @@
     @NonNull
     public static LocationRequest create() {
         // 60 minutes is the default legacy interval
-        return new LocationRequest.Builder(60 * 60 * 1000)
-                .setQuality(QUALITY_LOW_POWER)
-                .build();
+        return new LocationRequest.Builder(60 * 60 * 1000).build();
     }
 
     /**
@@ -239,7 +232,7 @@
         } else if (LocationManager.GPS_PROVIDER.equals(provider)) {
             quality = QUALITY_HIGH_ACCURACY;
         } else {
-            quality = POWER_LOW;
+            quality = QUALITY_BALANCED_POWER_ACCURACY;
         }
 
         return new LocationRequest.Builder(intervalMillis)
@@ -291,11 +284,11 @@
             long maxUpdateDelayMillis,
             boolean hiddenFromAppOps,
             boolean adasGnssBypass,
-            boolean locationSettingsIgnored,
+            boolean bypass,
             boolean lowPower,
             WorkSource workSource) {
         mProvider = provider;
-        mInterval = intervalMillis;
+        mIntervalMillis = intervalMillis;
         mQuality = quality;
         mMinUpdateIntervalMillis = minUpdateIntervalMillis;
         mExpireAtRealtimeMillis = expireAtRealtimeMillis;
@@ -305,7 +298,7 @@
         mMaxUpdateDelayMillis = maxUpdateDelayMillis;
         mHideFromAppOps = hiddenFromAppOps;
         mAdasGnssBypass = adasGnssBypass;
-        mLocationSettingsIgnored = locationSettingsIgnored;
+        mBypass = bypass;
         mLowPower = lowPower;
         mWorkSource = Objects.requireNonNull(workSource);
     }
@@ -354,7 +347,7 @@
                 mQuality = QUALITY_LOW_POWER;
                 break;
             case POWER_NONE:
-                mInterval = PASSIVE_INTERVAL;
+                mIntervalMillis = PASSIVE_INTERVAL;
                 break;
             default:
                 throw new IllegalArgumentException("invalid quality: " + quality);
@@ -388,9 +381,9 @@
             millis = Long.MAX_VALUE - 1;
         }
 
-        mInterval = millis;
-        if (mMinUpdateIntervalMillis > mInterval) {
-            mMinUpdateIntervalMillis = mInterval;
+        mIntervalMillis = millis;
+        if (mMinUpdateIntervalMillis > mIntervalMillis) {
+            mMinUpdateIntervalMillis = mIntervalMillis;
         }
         return this;
     }
@@ -418,7 +411,7 @@
      * @return the desired interval of location updates
      */
     public @IntRange(from = 0) long getIntervalMillis() {
-        return mInterval;
+        return mIntervalMillis;
     }
 
     /**
@@ -556,11 +549,11 @@
      */
     public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
         if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
-            return (long) (mInterval * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+            return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
         } else {
             // the min is only necessary in case someone use a deprecated function to mess with the
             // interval or min update interval
-            return min(mMinUpdateIntervalMillis, mInterval);
+            return min(mMinUpdateIntervalMillis, mIntervalMillis);
         }
     }
 
@@ -673,7 +666,7 @@
     @Deprecated
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) {
-        mLocationSettingsIgnored = locationSettingsIgnored;
+        mBypass = locationSettingsIgnored;
         return this;
     }
 
@@ -687,7 +680,7 @@
      */
     @SystemApi
     public boolean isLocationSettingsIgnored() {
-        return mLocationSettingsIgnored;
+        return mBypass;
     }
 
     /**
@@ -696,7 +689,7 @@
      * @hide
      */
     public boolean isBypass() {
-        return mAdasGnssBypass || mLocationSettingsIgnored;
+        return mAdasGnssBypass || mBypass;
     }
 
     /**
@@ -796,7 +789,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
         parcel.writeString(mProvider);
-        parcel.writeLong(mInterval);
+        parcel.writeLong(mIntervalMillis);
         parcel.writeInt(mQuality);
         parcel.writeLong(mExpireAtRealtimeMillis);
         parcel.writeLong(mDurationMillis);
@@ -806,7 +799,7 @@
         parcel.writeLong(mMaxUpdateDelayMillis);
         parcel.writeBoolean(mHideFromAppOps);
         parcel.writeBoolean(mAdasGnssBypass);
-        parcel.writeBoolean(mLocationSettingsIgnored);
+        parcel.writeBoolean(mBypass);
         parcel.writeBoolean(mLowPower);
         parcel.writeTypedObject(mWorkSource, 0);
     }
@@ -821,7 +814,7 @@
         }
 
         LocationRequest that = (LocationRequest) o;
-        return mInterval == that.mInterval
+        return mIntervalMillis == that.mIntervalMillis
                 && mQuality == that.mQuality
                 && mExpireAtRealtimeMillis == that.mExpireAtRealtimeMillis
                 && mDurationMillis == that.mDurationMillis
@@ -831,7 +824,7 @@
                 && mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
                 && mHideFromAppOps == that.mHideFromAppOps
                 && mAdasGnssBypass == that.mAdasGnssBypass
-                && mLocationSettingsIgnored == that.mLocationSettingsIgnored
+                && mBypass == that.mBypass
                 && mLowPower == that.mLowPower
                 && Objects.equals(mProvider, that.mProvider)
                 && Objects.equals(mWorkSource, that.mWorkSource);
@@ -839,7 +832,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mProvider, mInterval, mWorkSource);
+        return Objects.hash(mProvider, mIntervalMillis, mWorkSource);
     }
 
     @NonNull
@@ -850,9 +843,9 @@
         if (mProvider != null) {
             s.append(mProvider).append(" ");
         }
-        if (mInterval != PASSIVE_INTERVAL) {
+        if (mIntervalMillis != PASSIVE_INTERVAL) {
             s.append("@");
-            TimeUtils.formatDuration(mInterval, s);
+            TimeUtils.formatDuration(mIntervalMillis, s);
 
             switch (mQuality) {
                 case QUALITY_HIGH_ACCURACY:
@@ -879,14 +872,14 @@
             s.append(", maxUpdates=").append(mMaxUpdates);
         }
         if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL
-                && mMinUpdateIntervalMillis < mInterval) {
+                && mMinUpdateIntervalMillis < mIntervalMillis) {
             s.append(", minUpdateInterval=");
             TimeUtils.formatDuration(mMinUpdateIntervalMillis, s);
         }
         if (mMinUpdateDistanceMeters > 0.0) {
             s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters);
         }
-        if (mMaxUpdateDelayMillis / 2 > mInterval) {
+        if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) {
             s.append(", maxUpdateDelay=");
             TimeUtils.formatDuration(mMaxUpdateDelayMillis, s);
         }
@@ -899,8 +892,8 @@
         if (mAdasGnssBypass) {
             s.append(", adasGnssBypass");
         }
-        if (mLocationSettingsIgnored) {
-            s.append(", settingsBypass");
+        if (mBypass) {
+            s.append(", bypass");
         }
         if (mWorkSource != null && !mWorkSource.isEmpty()) {
             s.append(", ").append(mWorkSource);
@@ -923,7 +916,7 @@
         private long mMaxUpdateDelayMillis;
         private boolean mHiddenFromAppOps;
         private boolean mAdasGnssBypass;
-        private boolean mLocationSettingsIgnored;
+        private boolean mBypass;
         private boolean mLowPower;
         @Nullable private WorkSource mWorkSource;
 
@@ -943,7 +936,7 @@
             mMaxUpdateDelayMillis = 0;
             mHiddenFromAppOps = false;
             mAdasGnssBypass = false;
-            mLocationSettingsIgnored = false;
+            mBypass = false;
             mLowPower = false;
             mWorkSource = null;
         }
@@ -952,7 +945,7 @@
          * Creates a new Builder with all parameters copied from the given location request.
          */
         public Builder(@NonNull LocationRequest locationRequest) {
-            mIntervalMillis = locationRequest.mInterval;
+            mIntervalMillis = locationRequest.mIntervalMillis;
             mQuality = locationRequest.mQuality;
             mDurationMillis = locationRequest.mDurationMillis;
             mMaxUpdates = locationRequest.mMaxUpdates;
@@ -961,7 +954,7 @@
             mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis;
             mHiddenFromAppOps = locationRequest.mHideFromAppOps;
             mAdasGnssBypass = locationRequest.mAdasGnssBypass;
-            mLocationSettingsIgnored = locationRequest.mLocationSettingsIgnored;
+            mBypass = locationRequest.mBypass;
             mLowPower = locationRequest.mLowPower;
             mWorkSource = locationRequest.mWorkSource;
 
@@ -1160,14 +1153,15 @@
         @SystemApi
         @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
         public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) {
-            mLocationSettingsIgnored = locationSettingsIgnored;
+            mBypass = locationSettingsIgnored;
             return this;
         }
 
         /**
          * It set to true, indicates that extreme trade-offs should be made if possible to save
          * power for this request. This usually involves specialized hardware modes which can
-         * greatly affect the quality of locations. Defaults to false.
+         * greatly affect the quality of locations. Not all devices may support this. Defaults to
+         * false.
          *
          * <p>Permissions enforcement occurs when resulting location request is actually used, not
          * when this method is invoked.
@@ -1227,7 +1221,7 @@
                     mMaxUpdateDelayMillis,
                     mHiddenFromAppOps,
                     mAdasGnssBypass,
-                    mLocationSettingsIgnored,
+                    mBypass,
                     mLowPower,
                     new WorkSource(mWorkSource));
         }
diff --git a/media/aidl/android/media/audio/common/AudioDeviceType.aidl b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
index afe6d10..8e200de 100644
--- a/media/aidl/android/media/audio/common/AudioDeviceType.aidl
+++ b/media/aidl/android/media/audio/common/AudioDeviceType.aidl
@@ -168,4 +168,8 @@
      * Output into a speaker of a phone / table dock.
      */
     OUT_DOCK = 145,
+    /**
+     * Output to a broadcast group.
+     */
+    OUT_BROADCAST = 146,
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
index 0b7b77c..6a7b686 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioDeviceType.aidl
@@ -67,4 +67,5 @@
   OUT_SUBMIX = 143,
   OUT_TELEPHONY_TX = 144,
   OUT_DOCK = 145,
+  OUT_BROADCAST = 146,
 }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 211a50e..dd17dc6 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -177,6 +177,11 @@
      */
     public static final int TYPE_HDMI_EARC         = 29;
 
+    /**
+     * A device type describing a Bluetooth Low Energy (BLE) broadcast group.
+     */
+    public static final int TYPE_BLE_BROADCAST   = 30;
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -207,7 +212,8 @@
             TYPE_REMOTE_SUBMIX,
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
-            TYPE_ECHO_REFERENCE}
+            TYPE_ECHO_REFERENCE,
+            TYPE_BLE_BROADCAST}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -264,7 +270,8 @@
             TYPE_HEARING_AID,
             TYPE_BUILTIN_SPEAKER_SAFE,
             TYPE_BLE_HEADSET,
-            TYPE_BLE_SPEAKER}
+            TYPE_BLE_SPEAKER,
+            TYPE_BLE_BROADCAST}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -296,6 +303,7 @@
             case TYPE_BUILTIN_SPEAKER_SAFE:
             case TYPE_BLE_HEADSET:
             case TYPE_BLE_SPEAKER:
+            case TYPE_BLE_BROADCAST:
                 return true;
             default:
                 return false;
@@ -636,6 +644,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -690,6 +699,7 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST);
 
         // privileges mapping to input device
         EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index ebe0882..9211c53 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -90,7 +90,8 @@
      * {@link AudioManager#DEVICE_OUT_BLE_HEADSET}, {@link AudioManager#DEVICE_OUT_BLE_SPEAKER})
      * use the MAC address of the bluetooth device in the form "00:11:22:AA:BB:CC" as reported by
      * {@link BluetoothDevice#getAddress()}.
-     * - Deivces that do not have an address will indicate an empty string "".
+     * - Bluetooth LE broadcast group ({@link AudioManager#DEVICE_OUT_BLE_BROADCAST} use the group number.
+     * - Devices that do not have an address will indicate an empty string "".
      */
     public String address() {
         return mAddress;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 68e5d94..c4cef4c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5440,6 +5440,10 @@
      */
     public static final int DEVICE_OUT_BLE_SPEAKER = AudioSystem.DEVICE_OUT_BLE_SPEAKER;
     /** @hide
+     * The audio output device code for a BLE audio brodcast group.
+     */
+    public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 50bf1e5..306479a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -989,6 +989,8 @@
     public static final int DEVICE_OUT_BLE_HEADSET = 0x20000000;
     /** @hide */
     public static final int DEVICE_OUT_BLE_SPEAKER = 0x20000001;
+    /** @hide */
+    public static final int DEVICE_OUT_BLE_BROADCAST = 0x20000002;
 
     /** @hide */
     public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
@@ -1049,6 +1051,7 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_ECHO_CANCELLER);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_HEADSET);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_SPEAKER);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BLE_BROADCAST);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_DEFAULT);
 
         DEVICE_OUT_ALL_A2DP_SET = new HashSet<>();
@@ -1079,6 +1082,7 @@
         DEVICE_OUT_ALL_BLE_SET = new HashSet<>();
         DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_HEADSET);
         DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_SPEAKER);
+        DEVICE_OUT_ALL_BLE_SET.add(DEVICE_OUT_BLE_BROADCAST);
     }
 
     // input devices
@@ -1262,6 +1266,7 @@
     /** @hide */ public static final String DEVICE_OUT_ECHO_CANCELLER_NAME = "echo_canceller";
     /** @hide */ public static final String DEVICE_OUT_BLE_HEADSET_NAME = "ble_headset";
     /** @hide */ public static final String DEVICE_OUT_BLE_SPEAKER_NAME = "ble_speaker";
+    /** @hide */ public static final String DEVICE_OUT_BLE_BROADCAST_NAME = "ble_broadcast";
 
     /** @hide */ public static final String DEVICE_IN_COMMUNICATION_NAME = "communication";
     /** @hide */ public static final String DEVICE_IN_AMBIENT_NAME = "ambient";
@@ -1361,6 +1366,8 @@
             return DEVICE_OUT_BLE_HEADSET_NAME;
         case DEVICE_OUT_BLE_SPEAKER:
             return DEVICE_OUT_BLE_SPEAKER_NAME;
+        case DEVICE_OUT_BLE_BROADCAST:
+            return DEVICE_OUT_BLE_BROADCAST_NAME;
         case DEVICE_OUT_DEFAULT:
         default:
             return "0x" + Integer.toHexString(device);
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
index 71b1634..8e80a62 100644
--- a/media/java/android/media/tv/AitInfo.java
+++ b/media/java/android/media/tv/AitInfo.java
@@ -23,7 +23,6 @@
 
 /**
  * AIT (Application Information Table) info.
- * @hide
  */
 public final class AitInfo implements Parcelable {
     static final String TAG = "AitInfo";
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 9bc7367..1a9cab0 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -592,7 +592,11 @@
             });
         }
 
-        /** @hide */
+        /**
+         * Informs the application that this session has been tuned to the given channel.
+         *
+         * @param channelUri The URI of the tuned channel.
+         */
         public void notifyTuned(@NonNull Uri channelUri) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -947,12 +951,11 @@
         /**
          * Informs the app that the AIT (Application Information Table) is updated.
          *
-         * <p>This method should also be call when
+         * <p>This method should also be called when
          * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
          * info.
          *
          * @see #onSetInteractiveAppNotificationEnabled(boolean)
-         * @hide
          */
         public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1214,7 +1217,6 @@
          *
          * @see TvView#setInteractiveAppNotificationEnabled(boolean)
          * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
-         * @hide
          */
         public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
         }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index d2086c5..6c25a70 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -493,7 +493,6 @@
      *
      * @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
      * @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView)
-     * @hide
      */
     public void setInteractiveAppNotificationEnabled(boolean enabled) {
         if (mSession != null) {
@@ -1074,7 +1073,6 @@
          * This is called when the AIT (Application Information Table) info has been updated.
          *
          * @param aitInfo The current AIT info.
-         * @hide
          */
         public void onAitInfoUpdated(@NonNull String inputId, @NonNull AitInfo aitInfo) {
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 39be501..c5dfaa2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -57,7 +57,7 @@
 
 /**
  * Central system API to the overall TV interactive application framework (TIAF) architecture, which
- * arbitrates interaction between applications and interactive apps.
+ * arbitrates interaction between Android applications and TV interactive apps.
  */
 @SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
 public final class TvInteractiveAppManager {
@@ -75,22 +75,22 @@
 
     /**
      * Unrealized state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_UNREALIZED = 1;
     /**
      * Preparing state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_PREPARING = 2;
     /**
      * Ready state of interactive app service.
-     * @hide
+     *
+     * <p>In this state, the interactive app service is ready, and interactive apps can be started.
+     *
+     * @see TvInteractiveAppView#startInteractiveApp()
      */
     public static final int SERVICE_STATE_READY = 3;
     /**
      * Error state of interactive app service.
-     * @hide
      */
     public static final int SERVICE_STATE_ERROR = 4;
 
@@ -105,17 +105,14 @@
 
     /**
      * Stopped (or not started) state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
     /**
      * Running state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
     /**
      * Error state of interactive application.
-     * @hide
      */
     public static final int INTERACTIVE_APP_STATE_ERROR = 3;
 
@@ -136,46 +133,37 @@
 
     /**
      * No error.
-     * @hide
      */
     public static final int ERROR_NONE = 0;
     /**
      * Unknown error code.
-     * @hide
      */
     public static final int ERROR_UNKNOWN = 1;
     /**
      * Error code for an unsupported channel.
-     * @hide
      */
     public static final int ERROR_NOT_SUPPORTED = 2;
     /**
      * Error code for weak signal.
-     * @hide
      */
     public static final int ERROR_WEAK_SIGNAL = 3;
     /**
      * Error code when resource (e.g. tuner) is unavailable.
-     * @hide
      */
     public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
     /**
      * Error code for blocked contents.
-     * @hide
      */
     public static final int ERROR_BLOCKED = 5;
     /**
      * Error code when the key or module is missing for the encrypted channel.
-     * @hide
      */
     public static final int ERROR_ENCRYPTED = 6;
     /**
      * Error code when the current channel is an unknown channel.
-     * @hide
      */
     public static final int ERROR_UNKNOWN_CHANNEL = 7;
 
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
@@ -561,7 +549,6 @@
 
     /**
      * Callback used to monitor status of the TV Interactive App.
-     * @hide
      */
     public abstract static class TvInteractiveAppCallback {
         /**
@@ -796,7 +783,6 @@
      *
      * @param callback A callback used to monitor status of the TV Interactive App services.
      * @param executor A {@link Executor} that the status change will be delivered to.
-     * @hide
      */
     public void registerCallback(
             @NonNull TvInteractiveAppCallback callback,
@@ -812,7 +798,6 @@
      * Unregisters the existing {@link TvInteractiveAppCallback}.
      *
      * @param callback The existing callback to remove.
-     * @hide
      */
     public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
         Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index d599d0a..afa1ff7 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -34,6 +34,7 @@
 import android.media.tv.TvContentRating;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.TvView;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -65,7 +66,8 @@
 import java.util.List;
 
 /**
- * The TvInteractiveAppService class represents a TV interactive applications RTE.
+ * A TV interactive application service is a service that provides runtime environment and runs TV
+ * interactive applications.
  */
 public abstract class TvInteractiveAppService extends Service {
     private static final boolean DEBUG = false;
@@ -201,10 +203,8 @@
 
     /**
      * Prepares TV Interactive App service for the given type.
-     * @hide
      */
-    public void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type) {
-    }
+    public abstract void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
      * Registers App link info.
@@ -239,14 +239,11 @@
      *
      * @param iAppServiceId The ID of the TV Interactive App associated with the session.
      * @param type The type of the TV Interactive App associated with the session.
-     * @hide
      */
     @Nullable
-    public Session onCreateSession(
+    public abstract Session onCreateSession(
             @NonNull String iAppServiceId,
-            @TvInteractiveAppInfo.InteractiveAppType int type) {
-        return null;
-    }
+            @TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
      * Notifies the system when the state of the interactive app RTE has been changed.
@@ -256,7 +253,6 @@
      * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
      *              used when the state is not
      *              {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
-     * @hide
      */
     public final void notifyStateChanged(
             @TvInteractiveAppInfo.InteractiveAppType int type,
@@ -272,7 +268,12 @@
 
     /**
      * Base class for derived classes to implement to provide a TV interactive app session.
-     * @hide
+     *
+     * <p>A session is associated with a {@link TvInteractiveAppView} instance and handles
+     * corresponding communications. It also handles the communications with
+     * {@link android.media.tv.TvInputService.Session} if connected.
+     *
+     * @see TvInteractiveAppView#setTvView(TvView)
      */
     public abstract static class Session implements KeyEvent.Callback {
         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
@@ -359,8 +360,11 @@
         /**
          * Creates broadcast-independent(BI) interactive application.
          *
+         * <p>The implementation should call {@link #notifyBiInteractiveAppCreated(Uri, String)},
+         * no matter if it's created successfully or not.
+         *
+         * @see #notifyBiInteractiveAppCreated(Uri, String)
          * @see #onDestroyBiInteractiveApp(String)
-         * @hide
          */
         public void onCreateBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
         }
@@ -370,10 +374,9 @@
          * Destroys broadcast-independent(BI) interactive application.
          *
          * @param biIAppId the BI interactive app ID from
-         *        {@link #createBiInteractiveApp(Uri, Bundle)}
+         *                 {@link #onCreateBiInteractiveApp(Uri, Bundle)}}
          *
          * @see #onCreateBiInteractiveApp(Uri, Bundle)
-         * @hide
          */
         public void onDestroyBiInteractiveApp(@NonNull String biIAppId) {
         }
@@ -482,7 +485,8 @@
 
         /**
          * Called when the corresponding TV input tuned to a channel.
-         * @hide
+         *
+         * @param channelUri The tuned channel URI.
          */
         public void onTuned(@NonNull Uri channelUri) {
         }
@@ -1046,11 +1050,14 @@
 
         /**
          * Notifies the broadcast-independent(BI) interactive application has been created.
+         *
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
-         *                 app.
-         * @hide
+         *                 app. {@code null} if it's not created successfully.
+         *
+         * @see #onCreateBiInteractiveApp(Uri, Bundle)
          */
-        public final void notifyBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
+        public final void notifyBiInteractiveAppCreated(
+                @NonNull Uri biIAppUri, @Nullable String biIAppId) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 12e2199..2922bae 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -387,7 +387,6 @@
      *                      {@link TvInteractiveAppInfo#getId()}.
      *
      * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
-     * @hide
      */
     public void prepareInteractiveApp(
             @NonNull String iAppServiceId,
@@ -416,7 +415,6 @@
 
     /**
      * Stops the interactive application.
-     * @hide
      */
     public void stopInteractiveApp() {
         if (DEBUG) {
@@ -524,8 +522,10 @@
     /**
      * Creates broadcast-independent(BI) interactive application.
      *
-     * @see #destroyBiInteractiveApp(String)
-     * @hide
+     * <p>{@link TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)} will be
+     * called for the result.
+     *
+     * @see TvInteractiveAppCallback#onBiInteractiveAppCreated(String, Uri, String)
      */
     public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
         if (DEBUG) {
@@ -542,7 +542,6 @@
      * @param biIAppId the BI interactive app ID from {@link #createBiInteractiveApp(Uri, Bundle)}
      *
      * @see #createBiInteractiveApp(Uri, Bundle)
-     * @hide
      */
     public void destroyBiInteractiveApp(@NonNull String biIAppId) {
         if (DEBUG) {
@@ -564,7 +563,6 @@
      *
      * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
      * @return The result of the operation.
-     * @hide
      */
     public int setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -623,14 +621,13 @@
         }
 
         /**
-         * This is called when the session state is changed.
+         * This is called when the state of corresponding interactive app is changed.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param state the current state.
          * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE}
          *              is used when the state is not
          *              {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
-         * @hide
          */
         public void onStateChanged(
                 @NonNull String iAppServiceId,
@@ -643,10 +640,12 @@
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
-         *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
+         *                  {@link #createBiInteractiveApp(Uri, Bundle)}
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
-         *                 app.
-         * @hide
+         *                 app. {@code null} if it's not created successfully.
+         *
+         * @see #createBiInteractiveApp(Uri, Bundle)
+         * @see #destroyBiInteractiveApp(String)
          */
         public void onBiInteractiveAppCreated(@NonNull String iAppServiceId, @NonNull Uri biIAppUri,
                 @Nullable String biIAppId) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 3157375..be114d4 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -47,6 +47,7 @@
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tuner.frontend.FrontendStatus;
 import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.media.tv.tuner.frontend.FrontendStatusReadiness;
 import android.media.tv.tuner.frontend.OnTuneEventListener;
 import android.media.tv.tuner.frontend.ScanCallback;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
@@ -61,9 +62,7 @@
 import android.os.Message;
 import android.os.Process;
 import android.util.Log;
-
 import com.android.internal.util.FrameworkStatsLog;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -1005,6 +1004,7 @@
     private native int nativeRemoveOutputPid(int pid);
     private native Lnb nativeOpenLnbByHandle(int handle);
     private native Lnb nativeOpenLnbByName(String name);
+    private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes);
 
     private native Descrambler nativeOpenDescramblerByHandle(int handle);
     private native int nativeOpenDemuxByhandle(int handle);
@@ -1595,6 +1595,38 @@
     }
 
     /**
+     * Gets Frontend Status Readiness statuses for given status types.
+     *
+     * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported versions would cause
+     * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * @param statusTypes an array of status types.
+     *
+     * @return an array of current readiness states. {@code null} if the operation failed or
+     *         unsupported versions.
+     * @throws IllegalStateException if there is no active frontend currently.
+     */
+    @Nullable
+    @SuppressLint("ArrayReturn")
+    @SuppressWarnings("NullableCollection")
+    public FrontendStatusReadiness[] getFrontendStatusReadiness(
+            @NonNull @FrontendStatusType int[] statusTypes) {
+        mFrontendLock.lock();
+        try {
+            if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
+                return null;
+            }
+            if (mFrontend == null) {
+                throw new IllegalStateException("frontend is not initialized");
+            }
+            return nativeGetFrontendStatusReadiness(statusTypes);
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
      * Gets the currently initialized and activated frontend information. To get all the available
      * frontend info on the device, use {@link getAvailableFrontendInfos()}.
      *
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
new file mode 100644
index 0000000..52527b3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class contains the Frontend Status Readiness of a given type.
+ *
+ * @hide
+ */
+@SystemApi
+public class FrontendStatusReadiness {
+    /** @hide */
+    @IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE,
+            FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE,
+            FRONTEND_STATUS_READINESS_UNSUPPORTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Readiness {}
+
+    /**
+     * The FrontendStatus readiness status for the given FrontendStatusType is undefined.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNDEFINED =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNDEFINED;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is currently unavailable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNAVAILABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNAVAILABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is ready to read, but it’s unstable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNSTABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNSTABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is ready to read, and it’s stable.
+     */
+    public static final int FRONTEND_STATUS_READINESS_STABLE =
+            android.hardware.tv.tuner.FrontendStatusReadiness.STABLE;
+
+    /**
+     * The FrontendStatus for the given FrontendStatusType is not supported.
+     */
+    public static final int FRONTEND_STATUS_READINESS_UNSUPPORTED =
+            android.hardware.tv.tuner.FrontendStatusReadiness.UNSUPPORTED;
+
+    @FrontendStatusType private int mFrontendStatusType;
+    @Readiness private int mStatusReadiness;
+
+    private FrontendStatusReadiness(int type, int readiness) {
+        mFrontendStatusType = type;
+        mStatusReadiness = readiness;
+    }
+
+    /**
+     * Gets the frontend status type.
+     */
+    @FrontendStatusType
+    public int getStatusType() {
+        return mFrontendStatusType;
+    }
+    /**
+     * Gets the frontend status readiness.
+     */
+    @Readiness
+    public int getStatusReadiness() {
+        return mStatusReadiness;
+    }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 41f3a67..68dd8d0 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1631,6 +1631,36 @@
     return (jint)mFeClient->removeOutputPid(pid);
 }
 
+jobjectArray JTuner::getFrontendStatusReadiness(jintArray types) {
+    if (mFeClient == nullptr) {
+        ALOGE("frontend is not initialized");
+        return nullptr;
+    }
+
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jsize size = env->GetArrayLength(types);
+    jint intTypes[size];
+    env->GetIntArrayRegion(types, 0, size, intTypes);
+    std::vector<FrontendStatusType> v;
+    for (int i = 0; i < size; i++) {
+        v.push_back(static_cast<FrontendStatusType>(intTypes[i]));
+    }
+
+    vector<FrontendStatusReadiness> readiness = mFeClient->getStatusReadiness(v);
+    if (readiness.size() < size) {
+        return nullptr;
+    }
+
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatusReadiness");
+    jmethodID init = env->GetMethodID(clazz, "<init>", "(II)V");
+    jobjectArray valObj = env->NewObjectArray(size, clazz, nullptr);
+    for (int i = 0; i < size; i++) {
+        jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
+        env->SetObjectArrayElement(valObj, i, readinessObj);
+    }
+    return valObj;
+}
+
 jobject JTuner::openLnbByHandle(int handle) {
     if (mTunerClient == nullptr) {
         return nullptr;
@@ -4389,6 +4419,12 @@
     return tuner->removeOutputPid(pid);
 }
 
+static jobjectArray android_media_tv_Tuner_get_frontend_status_readiness(JNIEnv *env, jobject thiz,
+                                                                         jintArray types) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendStatusReadiness(types);
+}
+
 static jint android_media_tv_Tuner_close_frontend(JNIEnv* env, jobject thiz, jint /* handle */) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->closeFrontend();
@@ -4710,6 +4746,9 @@
             (void *)android_media_tv_Tuner_get_maximum_frontends },
     { "nativeRemoveOutputPid", "(I)I",
             (void *)android_media_tv_Tuner_remove_output_pid },
+    { "nativeGetFrontendStatusReadiness",
+            "([I)[Landroid/media/tv/tuner/frontend/FrontendStatusReadiness;",
+            (void *)android_media_tv_Tuner_get_frontend_status_readiness },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index e9475dc..03e7fa9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -206,6 +206,7 @@
     jint setMaxNumberOfFrontends(int32_t frontendType, int32_t maxNumber);
     int32_t getMaxNumberOfFrontends(int32_t frontendType);
     jint removeOutputPid(int32_t pid);
+    jobjectArray getFrontendStatusReadiness(jintArray types);
 
     jweak getObject();
 
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index bea0342..c6337ec 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -152,6 +152,16 @@
     return Result::INVALID_STATE;
 }
 
+vector<FrontendStatusReadiness> FrontendClient::getStatusReadiness(
+        const std::vector<FrontendStatusType>& statusTypes) {
+    vector<FrontendStatusReadiness> readiness;
+    if (mTunerFrontend != nullptr) {
+        mTunerFrontend->getFrontendStatusReadiness(statusTypes, &readiness);
+    }
+
+    return readiness;
+}
+
 shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
     return mTunerFrontend;
 }
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index c6838c8..85f6d56 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -35,6 +35,7 @@
 using ::aidl::android::hardware::tv::tuner::FrontendScanType;
 using ::aidl::android::hardware::tv::tuner::FrontendSettings;
 using ::aidl::android::hardware::tv::tuner::FrontendStatus;
+using ::aidl::android::hardware::tv::tuner::FrontendStatusReadiness;
 using ::aidl::android::hardware::tv::tuner::FrontendStatusType;
 using ::aidl::android::hardware::tv::tuner::FrontendType;
 using ::aidl::android::hardware::tv::tuner::Result;
@@ -125,6 +126,12 @@
      */
     Result removeOutputPid(int32_t pid);
 
+    /**
+     * Gets Frontend Status Readiness statuses for given status types.
+     */
+    vector<FrontendStatusReadiness> getStatusReadiness(
+            const std::vector<FrontendStatusType>& types);
+
     int32_t getId();
 
     shared_ptr<ITunerFrontend> getAidlFrontend();
diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp
index fbd4b2e..e22580d 100644
--- a/native/android/choreographer.cpp
+++ b/native/android/choreographer.cpp
@@ -67,7 +67,7 @@
         const AChoreographerFrameCallbackData* data) {
     return AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(data);
 }
-int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index) {
     return AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(data, index);
 }
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5b19102..d01a30e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -661,7 +661,7 @@
 }
 
 void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* aSurfaceTransaction,
-                                          int64_t vsyncId) {
+                                          AVsyncId vsyncId) {
     CHECK_NOT_NULL(aSurfaceTransaction);
     // TODO(b/210043506): Get start time from platform.
     ASurfaceTransaction_to_Transaction(aSurfaceTransaction)
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 8813f98..28f930f 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -39,14 +40,11 @@
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -57,6 +55,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Provides access to network usage history and statistics. Usage data is collected in
@@ -723,26 +722,36 @@
         }
     }
 
-    /** @hide */
-    public void registerUsageCallback(NetworkTemplate template, int networkType,
-            long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
+    /**
+     * Registers to receive notifications about data usage on specified networks.
+     *
+     * <p>The callbacks will continue to be called as long as the process is alive or
+     * {@link #unregisterUsageCallback} is called.
+     *
+     * @param template Template used to match networks. See {@link NetworkTemplate}.
+     * @param thresholdBytes Threshold in bytes to be notified on. The provided value that lower
+     *                       than 2MiB will be clamped for non-privileged callers.
+     * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+     *                 must run callback sequentially, otherwise the order of callbacks cannot be
+     *                 guaranteed.
+     * @param callback The {@link UsageCallback} that the system will call when data usage
+     *                 has exceeded the specified threshold.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void registerUsageCallback(@NonNull NetworkTemplate template, long thresholdBytes,
+            @NonNull @CallbackExecutor Executor executor, @NonNull UsageCallback callback) {
+        Objects.requireNonNull(template, "NetworkTemplate cannot be null");
         Objects.requireNonNull(callback, "UsageCallback cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
 
-        final Looper looper;
-        if (handler == null) {
-            looper = Looper.myLooper();
-        } else {
-            looper = handler.getLooper();
-        }
-
-        DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+        final DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
                 template, thresholdBytes);
         try {
-            CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
-                    template.getSubscriberId(), callback);
+            final UsageCallbackWrapper callbackWrapper =
+                    new UsageCallbackWrapper(executor, callback);
             callback.request = mService.registerUsageCallback(
-                    mContext.getOpPackageName(), request, new Messenger(callbackHandler),
-                    new Binder());
+                    mContext.getOpPackageName(), request, callbackWrapper);
             if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
 
             if (callback.request == null) {
@@ -795,12 +804,15 @@
         NetworkTemplate template = createTemplate(networkType, subscriberId);
         if (DBG) {
             Log.d(TAG, "registerUsageCallback called with: {"
-                + " networkType=" + networkType
-                + " subscriberId=" + subscriberId
-                + " thresholdBytes=" + thresholdBytes
-                + " }");
+                    + " networkType=" + networkType
+                    + " subscriberId=" + subscriberId
+                    + " thresholdBytes=" + thresholdBytes
+                    + " }");
         }
-        registerUsageCallback(template, networkType, thresholdBytes, callback, handler);
+
+        final Executor executor = handler == null ? r -> r.run() : r -> handler.post(r);
+
+        registerUsageCallback(template, thresholdBytes, executor, callback);
     }
 
     /**
@@ -825,6 +837,26 @@
      * Base class for usage callbacks. Should be extended by applications wanting notifications.
      */
     public static abstract class UsageCallback {
+        /**
+         * Called when data usage has reached the given threshold.
+         *
+         * Called by {@code NetworkStatsService} when the registered threshold is reached.
+         * If a caller implements {@link #onThresholdReached(NetworkTemplate)}, the system
+         * will not call {@link #onThresholdReached(int, String)}.
+         *
+         * @param template The {@link NetworkTemplate} that associated with this callback.
+         * @hide
+         */
+        @SystemApi(client = MODULE_LIBRARIES)
+        public void onThresholdReached(@NonNull NetworkTemplate template) {
+            // Backward compatibility for those who didn't override this function.
+            final int networkType = networkTypeForTemplate(template);
+            if (networkType != ConnectivityManager.TYPE_NONE) {
+                final String subscriberId = template.getSubscriberIds().isEmpty() ? null
+                        : template.getSubscriberIds().iterator().next();
+                onThresholdReached(networkType, subscriberId);
+            }
+        }
 
         /**
          * Called when data usage has reached the given threshold.
@@ -835,6 +867,25 @@
          * @hide used for internal bookkeeping
          */
         private DataUsageRequest request;
+
+        /**
+         * Get network type from a template if feasible.
+         *
+         * @param template the target {@link NetworkTemplate}.
+         * @return legacy network type, only supports for the types which is already supported in
+         *         {@link #registerUsageCallback(int, String, long, UsageCallback, Handler)}.
+         *         {@link ConnectivityManager#TYPE_NONE} for other types.
+         */
+        private static int networkTypeForTemplate(@NonNull NetworkTemplate template) {
+            switch (template.getMatchRule()) {
+                case NetworkTemplate.MATCH_MOBILE:
+                    return ConnectivityManager.TYPE_MOBILE;
+                case NetworkTemplate.MATCH_WIFI:
+                    return ConnectivityManager.TYPE_WIFI;
+                default:
+                    return ConnectivityManager.TYPE_NONE;
+            }
+        }
     }
 
     /**
@@ -953,43 +1004,32 @@
         }
     }
 
-    private static class CallbackHandler extends Handler {
-        private final int mNetworkType;
-        private final String mSubscriberId;
-        private UsageCallback mCallback;
+    private static class UsageCallbackWrapper extends IUsageCallback.Stub {
+        // Null if unregistered.
+        private volatile UsageCallback mCallback;
 
-        CallbackHandler(Looper looper, int networkType, String subscriberId,
-                UsageCallback callback) {
-            super(looper);
-            mNetworkType = networkType;
-            mSubscriberId = subscriberId;
+        private final Executor mExecutor;
+
+        UsageCallbackWrapper(@NonNull Executor executor, @NonNull UsageCallback callback) {
             mCallback = callback;
+            mExecutor = executor;
         }
 
         @Override
-        public void handleMessage(Message message) {
-            DataUsageRequest request =
-                    (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
-
-            switch (message.what) {
-                case CALLBACK_LIMIT_REACHED: {
-                    if (mCallback != null) {
-                        mCallback.onThresholdReached(mNetworkType, mSubscriberId);
-                    } else {
-                        Log.e(TAG, "limit reached with released callback for " + request);
-                    }
-                    break;
-                }
-                case CALLBACK_RELEASED: {
-                    if (DBG) Log.d(TAG, "callback released for " + request);
-                    mCallback = null;
-                    break;
-                }
+        public void onThresholdReached(DataUsageRequest request) {
+            // Copy it to a local variable in case mCallback changed inside the if condition.
+            final UsageCallback callback = mCallback;
+            if (callback != null) {
+                mExecutor.execute(() -> callback.onThresholdReached(request.template));
+            } else {
+                Log.e(TAG, "onThresholdReached with released callback for " + request);
             }
         }
 
-        private static Object getObject(Message msg, String key) {
-            return msg.getData().getParcelable(key);
+        @Override
+        public void onCallbackReleased(DataUsageRequest request) {
+            if (DBG) Log.d(TAG, "callback released for " + request);
+            mCallback = null;
         }
     }
 
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index da0aa99..efe626d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
@@ -24,6 +24,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.UnderlyingNetworkInfo;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
@@ -71,7 +72,7 @@
 
     /** Registers a callback on data usage. */
     DataUsageRequest registerUsageCallback(String callingPackage,
-            in DataUsageRequest request, in Messenger messenger, in IBinder binder);
+            in DataUsageRequest request, in IUsageCallback callback);
 
     /** Unregisters a callback on data usage. */
     void unregisterUsageRequest(in DataUsageRequest request);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 58ca21f..735c44d 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
 import static android.net.NetworkStats.IFACE_ALL;
@@ -34,6 +35,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.NetworkStatsHistory.Entry;
 import android.os.Binder;
 import android.service.NetworkStatsCollectionKeyProto;
 import android.service.NetworkStatsCollectionProto;
@@ -71,6 +74,8 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -80,7 +85,7 @@
  *
  * @hide
  */
-// @SystemApi(client = MODULE_LIBRARIES)
+@SystemApi(client = MODULE_LIBRARIES)
 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
     private static final String TAG = NetworkStatsCollection.class.getSimpleName();
     /** File header magic number: "ANET" */
@@ -810,6 +815,71 @@
     }
 
     /**
+     * Get the all historical stats of the collection {@link NetworkStatsCollection}.
+     *
+     * @return All {@link NetworkStatsHistory} in this collection.
+     */
+    @NonNull
+    public Map<Key, NetworkStatsHistory> getEntries() {
+        return new ArrayMap(mStats);
+    }
+
+    /**
+     * Builder class for {@link NetworkStatsCollection}.
+     */
+    public static final class Builder {
+        private final long mBucketDuration;
+        private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
+
+        /**
+         * Creates a new Builder with given bucket duration.
+         *
+         * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+         */
+        public Builder(long bucketDuration) {
+            mBucketDuration = bucketDuration;
+        }
+
+        /**
+         * Add association of the history with the specified key in this map.
+         *
+         * @param key The object used to identify a network, see {@link Key}.
+         * @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
+         * @return The builder object.
+         */
+        @NonNull
+        public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
+                @NonNull NetworkStatsHistory history) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(history);
+            final List<Entry> historyEntries = history.getEntries();
+
+            final NetworkStatsHistory.Builder historyBuilder =
+                    new NetworkStatsHistory.Builder(mBucketDuration, historyEntries.size());
+            for (Entry entry : historyEntries) {
+                historyBuilder.addEntry(entry);
+            }
+
+            mEntries.put(key, historyBuilder.build());
+            return this;
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkStatsCollection}.
+         *
+         * @return the built instance of {@link NetworkStatsCollection}.
+         */
+        @NonNull
+        public NetworkStatsCollection build() {
+            final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+            for (int i = 0; i < mEntries.size(); i++) {
+                collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
+            }
+            return collection;
+        }
+    }
+
+    /**
      * the identifier that associate with the {@link NetworkStatsHistory} object to identify
      * a certain record in the {@link NetworkStatsCollection} object.
      */
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index c803a72..c2f0cdf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.server.NetworkManagementSocketTagger;
 
@@ -210,10 +213,29 @@
         }
         final NetworkStatsManager statsManager =
                 context.getSystemService(NetworkStatsManager.class);
+        if (statsManager == null) {
+            // TODO: Currently Process.isSupplemental is not working yet, because it depends on
+            //  process to run in a certain UID range, which is not true for now. Change this
+            //  to Log.wtf once Process.isSupplemental is ready.
+            Log.e(TAG, "TrafficStats not initialized, uid=" + Binder.getCallingUid());
+            return;
+        }
         sStatsService = statsManager.getBinder();
     }
 
     /**
+     * Attach the socket tagger implementation to the current process, to
+     * get notified when a socket's {@link FileDescriptor} is assigned to
+     * a thread. See {@link SocketTagger#set(SocketTagger)}.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void attachSocketTagger() {
+        NetworkManagementSocketTagger.install();
+    }
+
+    /**
      * Set active tag to use when accounting {@link Socket} traffic originating
      * from the current thread. Only one active tag per thread is supported.
      * <p>
diff --git a/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
new file mode 100644
index 0000000..4e8a5b2
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/netstats/IUsageCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.net.netstats;
+
+import android.net.DataUsageRequest;
+
+/**
+ * Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
+ *
+ * @hide
+ */
+oneway interface IUsageCallback {
+    void onThresholdReached(in DataUsageRequest request);
+    void onCallbackReleased(in DataUsageRequest request);
+}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 36dd200..24bc91d 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -26,6 +26,8 @@
     srcs: [
         "src/com/android/server/net/NetworkIdentity*.java",
         "src/com/android/server/net/NetworkStats*.java",
+        "src/com/android/server/net/BpfInterfaceMapUpdater.java",
+        "src/com/android/server/net/InterfaceMapValue.java",
     ],
     path: "src",
     visibility: [
@@ -98,3 +100,28 @@
         "//packages/modules/Connectivity:__subpackages__",
     ],
 }
+
+cc_library_shared {
+    name: "libcom_android_net_module_util_jni",
+    min_sdk_version: "30",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+    srcs: [
+        "jni/onload.cpp",
+    ],
+    stl: "libc++_static",
+    static_libs: [
+        "libnet_utils_device_common_bpfjni",
+    ],
+    shared_libs: [
+        "liblog",
+        "libnativehelper",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/ConnectivityT/service/jni/onload.cpp b/packages/ConnectivityT/service/jni/onload.cpp
new file mode 100644
index 0000000..bca4697
--- /dev/null
+++ b/packages/ConnectivityT/service/jni/onload.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <log/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv *env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (register_com_android_net_module_util_BpfMap(env,
+            "com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+
+    return JNI_VERSION_1_6;
+}
+
+};
+
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java
new file mode 100644
index 0000000..25c88eb
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import android.content.Context;
+import android.net.INetd;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+/**
+ * Monitor interface added (without removed) and right interface name and its index to bpf map.
+ */
+public class BpfInterfaceMapUpdater {
+    private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
+    // This is current path but may be changed soon.
+    private static final String IFACE_INDEX_NAME_MAP_PATH =
+            "/sys/fs/bpf/map_netd_iface_index_name_map";
+    private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
+    private final INetd mNetd;
+    private final Handler mHandler;
+    private final Dependencies mDeps;
+
+    public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
+        this(ctx, handler, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
+        mDeps = deps;
+        mBpfMap = deps.getInterfaceMap();
+        mNetd = deps.getINetd(ctx);
+        mHandler = handler;
+    }
+
+    /**
+     * Dependencies of BpfInerfaceMapUpdater, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** Create BpfMap for updating interface and index mapping. */
+        public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+            try {
+                return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
+                    U32.class, InterfaceMapValue.class);
+            } catch (ErrnoException e) {
+                Log.e(TAG, "Cannot create interface map: " + e);
+                return null;
+            }
+        }
+
+        /** Get InterfaceParams for giving interface name. */
+        public InterfaceParams getInterfaceParams(String ifaceName) {
+            return InterfaceParams.getByName(ifaceName);
+        }
+
+        /** Get INetd binder object. */
+        public INetd getINetd(Context ctx) {
+            return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
+        }
+    }
+
+    /**
+     * Start listening interface update event.
+     * Query current interface names before listening.
+     */
+    public void start() {
+        mHandler.post(() -> {
+            if (mBpfMap == null) {
+                Log.wtf(TAG, "Fail to start: Null bpf map");
+                return;
+            }
+
+            try {
+                // TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
+                mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
+            }
+
+            final String[] ifaces;
+            try {
+                // TODO: use a netlink dump to get the current interface list.
+                ifaces = mNetd.interfaceGetList();
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.wtf(TAG, "Unable to query interface names by netd, " + e);
+                return;
+            }
+
+            for (String ifaceName : ifaces) {
+                addInterface(ifaceName);
+            }
+        });
+    }
+
+    private void addInterface(String ifaceName) {
+        final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
+            return;
+        }
+
+        try {
+            mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
+        }
+    }
+
+    private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
+        @Override
+        public void onInterfaceAdded(String ifName) {
+            mHandler.post(() -> addInterface(ifName));
+        }
+    }
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
new file mode 100644
index 0000000..061f323
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.net;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * The value of bpf interface index map which is used for NetworkStatsService.
+ */
+public class InterfaceMapValue extends Struct {
+    @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+    public final byte[] interfaceName;
+
+    public InterfaceMapValue(String iface) {
+        final byte[] ifaceArray = iface.getBytes();
+        interfaceName = new byte[16];
+        // All array bytes after the interface name, if any, must be 0.
+        System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
+    }
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
index bb123a3..17f3455 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
@@ -26,10 +26,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.INetd;
+import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkStats;
 import android.net.UnderlyingNetworkInfo;
-import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemClock;
 
@@ -70,7 +70,7 @@
 
     private final boolean mUseBpfStats;
 
-    private final INetd mNetd;
+    private final Context mContext;
 
     /**
      * Guards persistent data access in this class
@@ -158,12 +158,12 @@
         NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
     }
 
-    public NetworkStatsFactory(@NonNull INetd netd) {
-        this(new File("/proc/"), true, netd);
+    public NetworkStatsFactory(@NonNull Context ctx) {
+        this(ctx, new File("/proc/"), true);
     }
 
     @VisibleForTesting
-    public NetworkStatsFactory(File procRoot, boolean useBpfStats, @NonNull INetd netd) {
+    public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
@@ -172,7 +172,7 @@
             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
         }
-        mNetd = netd;
+        mContext = ctx;
     }
 
     public NetworkStats readBpfNetworkStatsDev() throws IOException {
@@ -295,11 +295,12 @@
     }
 
     @GuardedBy("mPersistentDataLock")
-    private void requestSwapActiveStatsMapLocked() throws RemoteException {
-        // Ask netd to do a active map stats swap. When the binder call successfully returns,
+    private void requestSwapActiveStatsMapLocked() {
+        // Do a active map stats swap. When the binder call successfully returns,
         // the system server should be able to safely read and clean the inactive map
         // without race problem.
-        mNetd.trafficSwapActiveStatsMap();
+        final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        cm.swapActiveStatsMap();
     }
 
     /**
@@ -327,7 +328,7 @@
                 if (mUseBpfStats) {
                     try {
                         requestSwapActiveStatsMapLocked();
-                    } catch (RemoteException e) {
+                    } catch (RuntimeException e) {
                         throw new IOException(e);
                     }
                     // Stats are always read from the inactive map, so they must be read after the
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index b57a4f9..1953624 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -26,13 +26,12 @@
 import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.os.Bundle;
+import android.net.netstats.IUsageCallback;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -75,10 +74,10 @@
      *
      * @return the normalized request wrapped within {@link RequestInfo}.
      */
-    public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
-                IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
-        DataUsageRequest request = buildRequest(inputRequest);
-        RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
+    public DataUsageRequest register(DataUsageRequest inputRequest, IUsageCallback callback,
+            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+        DataUsageRequest request = buildRequest(inputRequest, callingUid);
+        RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
                 accessLevel);
 
         if (LOGV) Log.v(TAG, "Registering observer for " + request);
@@ -195,10 +194,12 @@
         }
     }
 
-    private DataUsageRequest buildRequest(DataUsageRequest request) {
-        // Cap the minimum threshold to a safe default to avoid too many callbacks
-        long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
-        if (thresholdInBytes < request.thresholdInBytes) {
+    private DataUsageRequest buildRequest(DataUsageRequest request, int callingUid) {
+        // For non-system uid, cap the minimum threshold to a safe default to avoid too
+        // many callbacks.
+        long thresholdInBytes = (callingUid == Process.SYSTEM_UID ? request.thresholdInBytes
+                : Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
+        if (thresholdInBytes > request.thresholdInBytes) {
             Log.w(TAG, "Threshold was too low for " + request
                     + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
         }
@@ -206,11 +207,10 @@
                 request.template, thresholdInBytes);
     }
 
-    private RequestInfo buildRequestInfo(DataUsageRequest request,
-                Messenger messenger, IBinder binder, int callingUid,
-                @NetworkStatsAccess.Level int accessLevel) {
+    private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
+            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
         if (accessLevel <= NetworkStatsAccess.Level.USER) {
-            return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
+            return new UserUsageRequestInfo(this, request, callback, callingUid,
                     accessLevel);
         } else {
             // Safety check in case a new access level is added and we forgot to update this
@@ -218,7 +218,7 @@
                 throw new IllegalArgumentException(
                         "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
             }
-            return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
+            return new NetworkUsageRequestInfo(this, request, callback, callingUid,
                     accessLevel);
         }
     }
@@ -230,25 +230,23 @@
     private abstract static class RequestInfo implements IBinder.DeathRecipient {
         private final NetworkStatsObservers mStatsObserver;
         protected final DataUsageRequest mRequest;
-        private final Messenger mMessenger;
-        private final IBinder mBinder;
+        private final IUsageCallback mCallback;
         protected final int mCallingUid;
         protected final @NetworkStatsAccess.Level int mAccessLevel;
         protected NetworkStatsRecorder mRecorder;
         protected NetworkStatsCollection mCollection;
 
         RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
             mStatsObserver = statsObserver;
             mRequest = request;
-            mMessenger = messenger;
-            mBinder = binder;
+            mCallback = callback;
             mCallingUid = callingUid;
             mAccessLevel = accessLevel;
 
             try {
-                mBinder.linkToDeath(this, 0);
+                mCallback.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
                 binderDied();
             }
@@ -257,7 +255,7 @@
         @Override
         public void binderDied() {
             if (LOGV) {
-                Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mBinder + ")");
+                Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mCallback + ")");
             }
             mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
             callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -270,9 +268,7 @@
         }
 
         private void unlinkDeathRecipient() {
-            if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
-            }
+            mCallback.asBinder().unlinkToDeath(this, 0);
         }
 
         /**
@@ -294,17 +290,19 @@
         }
 
         private void callCallback(int callbackType) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
-            Message msg = Message.obtain();
-            msg.what = callbackType;
-            msg.setData(bundle);
             try {
                 if (LOGV) {
                     Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
                             + " for " + mRequest);
                 }
-                mMessenger.send(msg);
+                switch (callbackType) {
+                    case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
+                        mCallback.onThresholdReached(mRequest);
+                        break;
+                    case NetworkStatsManager.CALLBACK_RELEASED:
+                        mCallback.onCallbackReleased(mRequest);
+                        break;
+                }
             } catch (RemoteException e) {
                 // May occur naturally in the race of binder death.
                 Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
@@ -334,9 +332,9 @@
 
     private static class NetworkUsageRequestInfo extends RequestInfo {
         NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+            super(statsObserver, request, callback, callingUid, accessLevel);
         }
 
         @Override
@@ -376,9 +374,9 @@
 
     private static class UserUsageRequestInfo extends RequestInfo {
         UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    Messenger messenger, IBinder binder, int callingUid,
+                    IUsageCallback callback, int callingUid,
                     @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, messenger, binder, callingUid, accessLevel);
+            super(statsObserver, request, callback, callingUid, accessLevel);
         }
 
         @Override
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 1105de3..243d621 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -99,6 +99,7 @@
 import android.net.TrafficStats;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
+import android.net.netstats.IUsageCallback;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
@@ -110,7 +111,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -357,6 +357,9 @@
     @NonNull
     private final LocationPermissionChecker mLocationPermissionChecker;
 
+    @NonNull
+    private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+
     private static @NonNull File getDefaultSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
@@ -419,7 +422,7 @@
         final NetworkStatsService service = new NetworkStatsService(context,
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
-                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(netd),
+                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
                 new Dependencies());
 
@@ -454,6 +457,8 @@
         mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
                 mNetworkStatsSubscriptionsMonitor);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+        mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
+        mInterfaceMapUpdater.start();
     }
 
     /**
@@ -508,6 +513,13 @@
         public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
             return new LocationPermissionChecker(context);
         }
+
+        /** Create BpfInterfaceMapUpdater to update bpf interface map. */
+        @NonNull
+        public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+                @NonNull Context ctx, @NonNull Handler handler) {
+            return new BpfInterfaceMapUpdater(ctx, handler);
+        }
     }
 
     /**
@@ -988,8 +1000,17 @@
         }
 
         // TODO: switch to data layer stats once kernel exports
-        // for now, read network layer stats and flatten across all ifaces
-        final NetworkStats networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        // for now, read network layer stats and flatten across all ifaces.
+        // This function is used to query NeworkStats for calle's uid. The only caller method
+        // TrafficStats#getDataLayerSnapshotForUid alrady claim no special permission to query
+        // its own NetworkStats.
+        final long ident = Binder.clearCallingIdentity();
+        final NetworkStats networkLayer;
+        try {
+            networkLayer = readNetworkStatsUidDetail(uid, INTERFACES_ALL, TAG_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
 
         // splice in operation counts
         networkLayer.spliceOperationsFrom(mUidOperations);
@@ -1136,21 +1157,20 @@
     }
 
     @Override
-    public DataUsageRequest registerUsageCallback(String callingPackage,
-                DataUsageRequest request, Messenger messenger, IBinder binder) {
+    public DataUsageRequest registerUsageCallback(@NonNull String callingPackage,
+                @NonNull DataUsageRequest request, @NonNull IUsageCallback callback) {
         Objects.requireNonNull(callingPackage, "calling package is null");
         Objects.requireNonNull(request, "DataUsageRequest is null");
         Objects.requireNonNull(request.template, "NetworkTemplate is null");
-        Objects.requireNonNull(messenger, "messenger is null");
-        Objects.requireNonNull(binder, "binder is null");
+        Objects.requireNonNull(callback, "callback is null");
 
         int callingUid = Binder.getCallingUid();
         @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
         DataUsageRequest normalizedRequest;
         final long token = Binder.clearCallingIdentity();
         try {
-            normalizedRequest = mStatsObservers.register(request, messenger, binder,
-                    callingUid, accessLevel);
+            normalizedRequest = mStatsObservers.register(
+                    request, callback, callingUid, accessLevel);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 3c444f2..7168f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -402,7 +402,10 @@
         if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
             return;
         }
-        uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+        final Intent intent = new Intent()
+                .setComponent(dreamInfo.settingsComponentName)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        uiContext.startActivity(intent);
     }
 
     public void preview(DreamInfo dreamInfo) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
index e30aac5..a69c59c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartData.java
@@ -16,12 +16,18 @@
 
 package com.android.settingslib.net;
 
-import android.net.NetworkStatsHistory;
+import android.app.usage.NetworkStats;
+
+import java.util.List;
 
 public class ChartData {
-    public NetworkStatsHistory network;
+    // Collect the data usage history of the network from the given {@link NetworkTemplate}.
+    public List<NetworkStats.Bucket> network;
 
-    public NetworkStatsHistory detail;
-    public NetworkStatsHistory detailDefault;
-    public NetworkStatsHistory detailForeground;
+    // Collect the detail datausage history (foreground + Background).
+    public List<NetworkStats.Bucket> detail;
+    // Collect background datausage history.
+    public List<NetworkStats.Bucket> detailDefault;
+    // Collect foreground datausage history.
+    public List<NetworkStats.Bucket> detailForeground;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
index 60d22a0..573922f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoader.java
@@ -19,20 +19,21 @@
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.AsyncTaskLoader;
 import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
 import android.os.RemoteException;
 
 import com.android.settingslib.AppItem;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Framework loader is deprecated, use the compat version instead.
  *
@@ -42,26 +43,20 @@
 public class ChartDataLoader extends AsyncTaskLoader<ChartData> {
     private static final String KEY_TEMPLATE = "template";
     private static final String KEY_APP = "app";
-    private static final String KEY_FIELDS = "fields";
 
-    private final INetworkStatsSession mSession;
+    private final NetworkStatsManager mNetworkStatsManager;
     private final Bundle mArgs;
 
     public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
-        return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
-    }
-
-    public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
         final Bundle args = new Bundle();
         args.putParcelable(KEY_TEMPLATE, template);
         args.putParcelable(KEY_APP, app);
-        args.putInt(KEY_FIELDS, fields);
         return args;
     }
 
-    public ChartDataLoader(Context context, INetworkStatsSession session, Bundle args) {
+    public ChartDataLoader(Context context, NetworkStatsManager statsManager, Bundle args) {
         super(context);
-        mSession = session;
+        mNetworkStatsManager = statsManager;
         mArgs = args;
     }
 
@@ -75,10 +70,9 @@
     public ChartData loadInBackground() {
         final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
         final AppItem app = mArgs.getParcelable(KEY_APP);
-        final int fields = mArgs.getInt(KEY_FIELDS);
 
         try {
-            return loadInBackground(template, app, fields);
+            return loadInBackground(template, app);
         } catch (RemoteException e) {
             // since we can't do much without history, and we don't want to
             // leave with half-baked UI, we bail hard.
@@ -86,10 +80,22 @@
         }
     }
 
-    private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
+    @NonNull
+    private List<NetworkStats.Bucket> convertToBuckets(@NonNull NetworkStats stats) {
+        final List<NetworkStats.Bucket> ret = new ArrayList<>();
+        while (stats.hasNextBucket()) {
+            final NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+            stats.getNextBucket(bucket);
+            ret.add(bucket);
+        }
+        return ret;
+    }
+
+    private ChartData loadInBackground(NetworkTemplate template, AppItem app)
             throws RemoteException {
         final ChartData data = new ChartData();
-        data.network = mSession.getHistoryForNetwork(template, fields);
+        data.network = convertToBuckets(mNetworkStatsManager.queryDetailsForDevice(
+                template, Long.MIN_VALUE, Long.MAX_VALUE));
 
         if (app != null) {
             // load stats for current uid and template
@@ -103,13 +109,13 @@
             }
 
             if (size > 0) {
-                data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
-                data.detail.recordEntireHistory(data.detailDefault);
-                data.detail.recordEntireHistory(data.detailForeground);
+                data.detail = new ArrayList<>();
+                data.detail.addAll(data.detailDefault);
+                data.detail.addAll(data.detailForeground);
             } else {
-                data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
-                data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
-                data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
+                data.detailDefault = new ArrayList<>();
+                data.detailForeground = new ArrayList<>();
+                data.detail = new ArrayList<>();
             }
         }
 
@@ -129,17 +135,17 @@
     }
 
     /**
-     * Collect {@link NetworkStatsHistory} for the requested UID, combining with
-     * an existing {@link NetworkStatsHistory} if provided.
+     * Collect {@link List<NetworkStats.Bucket>} for the requested UID, combining with
+     * an existing {@link List<NetworkStats.Bucket>} if provided.
      */
-    private NetworkStatsHistory collectHistoryForUid(
-            NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
-            throws RemoteException {
-        final NetworkStatsHistory history = mSession.getHistoryForUid(
-                template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+    private List<NetworkStats.Bucket> collectHistoryForUid(
+            NetworkTemplate template, int uid, int set, List<NetworkStats.Bucket> existing) {
+        final List<NetworkStats.Bucket> history = convertToBuckets(
+                mNetworkStatsManager.queryDetailsForUidTagState(template,
+                        Long.MIN_VALUE, Long.MAX_VALUE, uid, TAG_NONE, set));
 
         if (existing != null) {
-            existing.recordEntireHistory(history);
+            existing.addAll(history);
             return existing;
         } else {
             return history;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
index 649aeff..8da6032 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoader.java
@@ -16,13 +16,12 @@
 
 package com.android.settingslib.net;
 
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
 import android.content.AsyncTaskLoader;
 import android.content.Context;
-import android.net.INetworkStatsSession;
-import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
-import android.os.RemoteException;
 
 /**
  * Framework loader is deprecated, use the compat version instead.
@@ -35,7 +34,7 @@
     private static final String KEY_START = "start";
     private static final String KEY_END = "end";
 
-    private final INetworkStatsSession mSession;
+    private final NetworkStatsManager mNetworkStatsManager;
     private final Bundle mArgs;
 
     public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
@@ -46,9 +45,9 @@
         return args;
     }
 
-    public SummaryForAllUidLoader(Context context, INetworkStatsSession session, Bundle args) {
+    public SummaryForAllUidLoader(Context context, Bundle args) {
         super(context);
-        mSession = session;
+        mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
         mArgs = args;
     }
 
@@ -63,12 +62,7 @@
         final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
         final long start = mArgs.getLong(KEY_START);
         final long end = mArgs.getLong(KEY_END);
-
-        try {
-            return mSession.getSummaryForAllUid(template, start, end, false);
-        } catch (RemoteException e) {
-            return null;
-        }
+        return mNetworkStatsManager.querySummary(template, start, end);
     }
 
     @Override
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46e24fa..c6fbfd8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -110,6 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
     <uses-permission android:name="android.permission.READ_INSTALL_SESSIONS" />
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+    <uses-permission android:name="android.permission.SEND_LOST_MODE_LOCATION_UPDATES" />
     <!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <!-- ACCESS_MTP is needed for testing purposes only. -->
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 3ae85e7..8323e32 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -71,6 +71,7 @@
 yurilin@google.com
 xuqiu@google.com
 zakcohen@google.com
+jernej@google.com
 
 #Android Auto
 hseog@google.com
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index da9a92a..68c8c3e 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -122,6 +122,11 @@
          * Set or clear device media playing
          */
         void setMediaTarget(@Nullable SmartspaceTarget target);
+
+        /**
+         * Get the index of the currently selected page.
+         */
+        int getSelectedPage();
     }
 
     /** Interface for launching Intents, which can differ on the lockscreen */
diff --git a/packages/SystemUI/res/drawable/ic_warning.xml b/packages/SystemUI/res/drawable/ic_warning.xml
new file mode 100644
index 0000000..fbed779
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_warning.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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">
+    <path android:fillColor="@android:color/white" android:pathData="M12,12.5zM1,21L12,2l11,19zM11,15h2v-5h-2zM12,18q0.425,0 0.713,-0.288Q13,17.425 13,17t-0.287,-0.712Q12.425,16 12,16t-0.713,0.288Q11,16.575 11,17t0.287,0.712Q11.575,18 12,18zM4.45,19h15.1L12,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_border.xml b/packages/SystemUI/res/drawable/overlay_border.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_border.xml
rename to packages/SystemUI/res/drawable/overlay_border.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/overlay_cancel.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_cancel.xml
rename to packages/SystemUI/res/drawable/overlay_cancel.xml
diff --git a/packages/SystemUI/res/drawable/screenshot_preview_background.xml b/packages/SystemUI/res/drawable/overlay_preview_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/screenshot_preview_background.xml
rename to packages/SystemUI/res/drawable/overlay_preview_background.xml
diff --git a/packages/SystemUI/res/layout/clipboard_content_preview.xml b/packages/SystemUI/res/layout/clipboard_content_preview.xml
deleted file mode 100644
index 7317a94..0000000
--- a/packages/SystemUI/res/layout/clipboard_content_preview.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             xmlns:app="http://schemas.android.com/apk/res-auto"
-             android:id="@+id/preview_border"
-             android:elevation="9dp"
-             android:layout_width="wrap_content"
-             android:layout_height="wrap_content"
-             android:layout_marginStart="@dimen/screenshot_offset_x"
-             android:layout_marginBottom="@dimen/screenshot_offset_y"
-             android:layout_gravity="bottom|start"
-             app:layout_constraintStart_toStartOf="parent"
-             app:layout_constraintBottom_toBottomOf="parent"
-             android:clipToPadding="false"
-             android:clipChildren="false"
-             android:padding="4dp"
-             android:background="@drawable/screenshot_border"
-             >
-    <FrameLayout
-        android:elevation="0dp"
-        android:background="@drawable/screenshot_preview_background"
-        android:clipChildren="true"
-        android:clipToOutline="true"
-        android:clipToPadding="true"
-        android:layout_width="@dimen/screenshot_x_scale"
-        android:layout_height="wrap_content">
-        <TextView android:id="@+id/text_preview"
-                  android:textFontWeight="500"
-                  android:padding="8dp"
-                  android:gravity="center|start"
-                  android:ellipsize="end"
-                  android:autoSizeTextType="uniform"
-                  android:autoSizeMinTextSize="10sp"
-                  android:autoSizeMaxTextSize="200sp"
-                  android:textColor="?android:attr/textColorPrimary"
-                  android:layout_width="@dimen/screenshot_x_scale"
-                  android:layout_height="@dimen/screenshot_x_scale"/>
-        <ImageView
-            android:id="@+id/image_preview"
-            android:scaleType="fitCenter"
-            android:adjustViewBounds="true"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-    </FrameLayout>
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 76280d8..7ffb3b2 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -17,9 +17,8 @@
 <com.android.systemui.clipboardoverlay.DraggableConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_gravity="bottom"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="match_parent">
     <ImageView
         android:id="@+id/actions_container_background"
         android:visibility="gone"
@@ -57,5 +56,81 @@
                      android:id="@+id/edit_chip"/>
         </LinearLayout>
     </HorizontalScrollView>
-    <include layout="@layout/clipboard_content_preview" />
+    <View
+        android:id="@+id/preview_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="@dimen/overlay_offset_y"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:elevation="@dimen/overlay_preview_elevation"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
+        android:background="@drawable/overlay_border"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierMargin="@dimen/overlay_border_width"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_preview_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:barrierDirection="top"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
+        app:constraint_referenced_ids="clipboard_preview"/>
+    <FrameLayout
+        android:id="@+id/clipboard_preview"
+        android:elevation="@dimen/overlay_preview_elevation"
+        android:background="@drawable/overlay_preview_background"
+        android:clipChildren="true"
+        android:clipToOutline="true"
+        android:clipToPadding="true"
+        android:layout_width="@dimen/clipboard_preview_size"
+        android:layout_margin="@dimen/overlay_border_width"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        app:layout_constraintBottom_toBottomOf="@id/preview_border"
+        app:layout_constraintStart_toStartOf="@id/preview_border"
+        app:layout_constraintEnd_toEndOf="@id/preview_border"
+        app:layout_constraintTop_toTopOf="@id/preview_border">
+        <TextView android:id="@+id/text_preview"
+                  android:textFontWeight="500"
+                  android:padding="8dp"
+                  android:gravity="center|start"
+                  android:ellipsize="end"
+                  android:autoSizeTextType="uniform"
+                  android:autoSizeMinTextSize="10sp"
+                  android:autoSizeMaxTextSize="200sp"
+                  android:textColor="?android:attr/textColorPrimary"
+                  android:layout_width="@dimen/clipboard_preview_size"
+                  android:layout_height="@dimen/clipboard_preview_size"/>
+        <ImageView
+            android:id="@+id/image_preview"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="@dimen/overlay_dismiss_button_elevation"
+        android:visibility="gone"
+        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
+        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+        android:contentDescription="@string/clipboard_dismiss_description">
+        <ImageView
+            android:id="@+id/dismiss_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
+    </FrameLayout>
 </com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
index f898ef6..5135947 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml
@@ -18,7 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/dream_overlay_complications_layer"
-    android:padding="20dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <TextClock
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index c6b502e..4929f50 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -28,8 +28,6 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent" />
 
-    <include layout="@layout/dream_overlay_complications_layer" />
-
     <com.android.systemui.dreams.DreamOverlayStatusBarView
         android:id="@+id/dream_overlay_status_bar"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 2d082dc..a5fdcd9 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -28,8 +28,8 @@
 
     <com.android.internal.widget.CachingIconView
         android:id="@+id/app_icon"
-        android:layout_width="@dimen/media_ttt_icon_size"
-        android:layout_height="@dimen/media_ttt_icon_size"
+        android:layout_width="@dimen/media_ttt_app_icon_size"
+        android:layout_height="@dimen/media_ttt_app_icon_size"
         android:layout_marginEnd="12dp"
         />
 
@@ -41,23 +41,34 @@
         android:textColor="?android:attr/textColorPrimary"
         />
 
+    <!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
+
     <ProgressBar
         android:id="@+id/loading"
         android:indeterminate="true"
-        android:layout_width="@dimen/media_ttt_loading_size"
-        android:layout_height="@dimen/media_ttt_loading_size"
-        android:layout_marginStart="12dp"
+        android:layout_width="@dimen/media_ttt_status_icon_size"
+        android:layout_height="@dimen/media_ttt_status_icon_size"
+        android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
         android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
         style="?android:attr/progressBarStyleSmall"
         />
 
+    <ImageView
+        android:id="@+id/failure_icon"
+        android:layout_width="@dimen/media_ttt_status_icon_size"
+        android:layout_height="@dimen/media_ttt_status_icon_size"
+        android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
+        android:src="@drawable/ic_warning"
+        android:tint="@color/GM2_red_500"
+        />
+
     <TextView
         android:id="@+id/undo"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/media_transfer_undo"
         android:textColor="?androidprv:attr/textColorOnAccent"
-        android:layout_marginStart="12dp"
+        android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
         android:textSize="@dimen/media_ttt_text_size"
         android:paddingStart="@dimen/media_ttt_chip_outer_padding"
         android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 7c0ee66..227212b 100644
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ b/packages/SystemUI/res/layout/screenshot.xml
@@ -40,7 +40,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="gone"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:src="@android:color/white"/>
     <com.android.systemui.screenshot.ScreenshotSelectorView
         android:id="@+id/screenshot_selector"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index f6e3f1a..8f791c3 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -63,20 +63,20 @@
         android:id="@+id/screenshot_preview_border"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:layout_marginStart="@dimen/screenshot_offset_x"
-        android:layout_marginBottom="@dimen/screenshot_offset_y"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:layout_marginStart="@dimen/overlay_offset_x"
+        android:layout_marginBottom="@dimen/overlay_offset_y"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:alpha="0"
-        android:background="@drawable/screenshot_border"
+        android:background="@drawable/overlay_border"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_end"
-        app:layout_constraintTop_toTopOf="@+id/screenshot_preview_top"/>
+        app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end"
+        app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/>
     <androidx.constraintlayout.widget.Barrier
         android:id="@+id/screenshot_preview_end"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        app:barrierMargin="4dp"
+        app:barrierMargin="@dimen/overlay_border_width"
         app:barrierDirection="end"
         app:constraint_referenced_ids="screenshot_preview"/>
     <androidx.constraintlayout.widget.Barrier
@@ -84,30 +84,30 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         app:barrierDirection="top"
-        app:barrierMargin="-4dp"
+        app:barrierMargin="@dimen/overlay_border_width_neg"
         app:constraint_referenced_ids="screenshot_preview"/>
     <ImageView
         android:id="@+id/screenshot_preview"
         android:visibility="invisible"
         android:layout_width="@dimen/screenshot_x_scale"
-        android:layout_margin="4dp"
+        android:layout_margin="@dimen/overlay_border_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:elevation="@dimen/screenshot_preview_elevation"
+        android:elevation="@dimen/overlay_preview_elevation"
         android:contentDescription="@string/screenshot_edit_description"
         android:scaleType="fitEnd"
-        android:background="@drawable/screenshot_preview_background"
+        android:background="@drawable/overlay_preview_background"
         android:adjustViewBounds="true"
-        app:layout_constraintBottom_toBottomOf="@+id/screenshot_preview_border"
-        app:layout_constraintStart_toStartOf="@+id/screenshot_preview_border"
-        app:layout_constraintEnd_toEndOf="@+id/screenshot_preview_border"
-        app:layout_constraintTop_toTopOf="@+id/screenshot_preview_border">
+        app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+        app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
+        app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
+        app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
     </ImageView>
     <FrameLayout
         android:id="@+id/screenshot_dismiss_button"
-        android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
-        android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
-        android:elevation="7dp"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:elevation="@dimen/overlay_dismiss_button_elevation"
         android:visibility="gone"
         app:layout_constraintStart_toEndOf="@id/screenshot_preview"
         app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
@@ -118,8 +118,8 @@
             android:id="@+id/screenshot_dismiss_image"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_margin="@dimen/screenshot_dismiss_button_margin"
-            android:src="@drawable/screenshot_cancel"/>
+            android:layout_margin="@dimen/overlay_dismiss_button_margin"
+            android:src="@drawable/overlay_cancel"/>
     </FrameLayout>
     <ImageView
         android:id="@+id/screenshot_scrollable_preview"
@@ -129,5 +129,5 @@
         android:visibility="gone"
         app:layout_constraintStart_toStartOf="@id/screenshot_preview"
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
-        android:elevation="@dimen/screenshot_preview_elevation"/>
+        android:elevation="@dimen/overlay_preview_elevation"/>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b12db5d..67d5b2f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -261,11 +261,6 @@
     <!-- The padding on the global screenshot background image -->
     <dimen name="screenshot_x_scale">80dp</dimen>
     <dimen name="screenshot_bg_protection_height">242dp</dimen>
-    <dimen name="screenshot_preview_elevation">4dp</dimen>
-    <dimen name="screenshot_offset_y">8dp</dimen>
-    <dimen name="screenshot_offset_x">16dp</dimen>
-    <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
-    <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">18dp</dimen>
     <dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
     <dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -289,6 +284,19 @@
     <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
     <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
 
+    <!-- Dimensions shared between "overlays" (clipboard and screenshot preview UIs) -->
+    <dimen name="overlay_offset_y">8dp</dimen>
+    <dimen name="overlay_offset_x">16dp</dimen>
+    <dimen name="overlay_preview_elevation">4dp</dimen>
+    <dimen name="overlay_dismiss_button_elevation">7dp</dimen>
+    <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen>
+    <dimen name="overlay_dismiss_button_margin">8dp</dimen>
+    <dimen name="overlay_border_width">4dp</dimen>
+    <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 -->
+    <dimen name="overlay_border_width_neg">-4dp</dimen>
+
+    <dimen name="clipboard_preview_size">@dimen/screenshot_x_scale</dimen>
+
 
     <!-- The width of the view containing navigation buttons -->
     <dimen name="navigation_key_width">70dp</dimen>
@@ -984,10 +992,11 @@
     <!-- Media tap-to-transfer chip for sender device -->
     <dimen name="media_ttt_chip_outer_padding">16dp</dimen>
     <dimen name="media_ttt_text_size">16sp</dimen>
-    <dimen name="media_ttt_icon_size">24dp</dimen>
-    <dimen name="media_ttt_loading_size">20dp</dimen>
+    <dimen name="media_ttt_app_icon_size">24dp</dimen>
+    <dimen name="media_ttt_status_icon_size">20dp</dimen>
     <dimen name="media_ttt_undo_button_vertical_padding">8dp</dimen>
     <dimen name="media_ttt_undo_button_vertical_negative_margin">-8dp</dimen>
+    <dimen name="media_ttt_last_item_start_margin">12dp</dimen>
 
     <!-- Media tap-to-transfer chip for receiver device -->
     <dimen name="media_ttt_chip_size_receiver">100dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7441a81..7384ccc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2158,8 +2158,14 @@
     <string name="media_transfer_undo">Undo</string>
     <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to play media on the different device. [CHAR LIMIT=75] -->
     <string name="media_move_closer_to_start_cast">Move closer to play on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+    <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] -->
+    <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
     <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
-    <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+    <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
+    <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
+    <string name="media_transfer_playing_this_device">Playing on this phone</string>
+    <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
+    <string name="media_transfer_failed">Something went wrong</string>
 
     <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
     <string name="controls_error_timeout">Inactive, check app</string>
@@ -2358,4 +2364,6 @@
     <string name="clipboard_edit_text_copy">Copy</string>
     <!-- Text informing user that content has been copied to the system clipboard [CHAR LIMIT=NONE] -->
     <string name="clipboard_overlay_text_copied">Copied</string>
+    <!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] -->
+    <string name="clipboard_dismiss_description">Dismiss copy UI</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
deleted file mode 100644
index 484791d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 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.shared.mediattt;
-
-import android.media.MediaRoute2Info;
-import com.android.systemui.shared.mediattt.DeviceInfo;
-
-/**
- * A callback interface that can be invoked to trigger media transfer events on System UI.
- *
- * This interface is for the *sender* device, which is the device currently playing media. This
- * sender device can transfer the media to a different device, called the receiver.
- *
- * System UI will implement this interface and other services will invoke it.
- */
-interface IDeviceSenderCallback {
-    /**
-     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
-     * the user can potentially *start* a cast to the receiver device if the user moves their device
-     * a bit closer.
-     *
-     * Important notes:
-     *   - When this callback triggers, the device is close enough to inform the user that
-     *     transferring is an option, but the device is *not* close enough to actually initiate a
-     *     transfer yet.
-     *   - This callback is for *starting* a cast. It should be used when this device is currently
-     *     playing media locally and the media should be transferred to be played on the receiver
-     *     device instead.
-     */
-    oneway void closeToReceiverToStartCast(
-        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
new file mode 100644
index 0000000..eb1c9d0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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.shared.mediattt;
+
+import android.media.MediaRoute2Info;
+import com.android.systemui.shared.mediattt.DeviceInfo;
+import com.android.systemui.shared.mediattt.IUndoTransferCallback;
+
+/**
+ * An interface that can be invoked to trigger media transfer events on System UI.
+ *
+ * This interface is for the *sender* device, which is the device currently playing media. This
+ * sender device can transfer the media to a different device, called the receiver.
+ *
+ * System UI will implement this interface and other services will invoke it.
+ */
+interface IDeviceSenderService {
+    /**
+     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+     * the user can potentially *start* a cast to the receiver device if the user moves their device
+     * a bit closer.
+     *
+     * Important notes:
+     *   - When this callback triggers, the device is close enough to inform the user that
+     *     transferring is an option, but the device is *not* close enough to actually initiate a
+     *     transfer yet.
+     *   - This callback is for *starting* a cast. It should be used when this device is currently
+     *     playing media locally and the media should be transferred to be played on the receiver
+     *     device instead.
+     */
+    oneway void closeToReceiverToStartCast(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+    /**
+     * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+     * the user can potentially *end* a cast on the receiver device if the user moves this device a
+     * bit closer.
+     *
+     * Important notes:
+     *   - When this callback triggers, the device is close enough to inform the user that
+     *     transferring is an option, but the device is *not* close enough to actually initiate a
+     *     transfer yet.
+     *   - This callback is for *ending* a cast. It should be used when media is currently being
+     *     played on the receiver device and the media should be transferred to play locally
+     *     instead.
+     */
+    oneway void closeToReceiverToEndCast(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+    /**
+     * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
+     * device has been started.
+     *
+     * Important notes:
+     *   - This callback is for *starting* a cast. It should be used when this device is currently
+     *     playing media locally and the media has started being transferred to the receiver device
+     *     instead.
+     */
+    oneway void transferToReceiverTriggered(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+    /**
+     * Invoke to notify System UI that a media transfer from the receiver and back to this device
+     * (the sender) has been started.
+     *
+     * Important notes:
+     *   - This callback is for *ending* a cast. It should be used when media is currently being
+     *     played on the receiver device and the media has started being transferred to play locally
+     *     instead.
+     */
+    oneway void transferToThisDeviceTriggered(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+    /**
+     * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver
+     * device has finished successfully.
+     *
+     * Important notes:
+     *   - This callback is for *starting* a cast. It should be used when this device had previously
+     *     been playing media locally and the media has successfully been transferred to the
+     *     receiver device instead.
+     *
+     * @param undoCallback will be invoked if the user chooses to undo this transfer.
+     */
+    oneway void transferToReceiverSucceeded(
+        in MediaRoute2Info mediaInfo,
+        in DeviceInfo otherDeviceInfo,
+        in IUndoTransferCallback undoCallback);
+
+    /**
+     * Invoke to notify System UI that a media transfer from the receiver and back to this device
+     * (the sender) has finished successfully.
+     *
+     * Important notes:
+     *   - This callback is for *ending* a cast. It should be used when media was previously being
+     *     played on the receiver device and has been successfully transferred to play locally on
+     *     this device instead.
+     *
+     * @param undoCallback will be invoked if the user chooses to undo this transfer.
+     */
+    oneway void transferToThisDeviceSucceeded(
+        in MediaRoute2Info mediaInfo,
+        in DeviceInfo otherDeviceInfo,
+        in IUndoTransferCallback undoCallback);
+
+    /**
+     * Invoke to notify System UI that the attempted transfer has failed.
+     *
+     * This callback will be used for both the transfer that should've *started* playing the media
+     * on the receiver and the transfer that should've *ended* the playing on the receiver.
+     */
+    oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+    /**
+     * Invoke to notify System UI that this device is no longer close to the receiver device.
+     */
+    oneway void noLongerCloseToReceiver(
+        in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
new file mode 100644
index 0000000..b47be87
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.shared.mediattt;
+
+/**
+ * An interface that will be invoked by System UI if the user choose to undo a transfer.
+ *
+ * Other services will implement this interface and System UI will invoke it.
+ */
+interface IUndoTransferCallback {
+
+    /**
+     * Invoked by SystemUI when the user requests to undo the media transfer that just occurred.
+     *
+     * Implementors of this method are repsonsible for actually undoing the transfer.
+     */
+    oneway void onUndoTriggered();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 54c798c..5d092d0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -55,8 +55,8 @@
     // See IStartingWindow.aidl
     public static final String KEY_EXTRA_SHELL_STARTING_WINDOW =
             "extra_shell_starting_window";
-    // See ISmartspaceTransitionController.aidl
-    public static final String KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER = "smartspace_transition";
+    // See ISysuiUnlockAnimationController.aidl
+    public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
     // See IRecentTasks.aidl
     public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index f2f382d..ace7938 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -277,7 +277,6 @@
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash);
                 t.reparent(target.leash, mInfo.getRootLeash());
                 t.setLayer(target.leash, layer);
-                t.hide(target.leash);
                 targets[i] = target;
             }
             t.apply();
@@ -332,13 +331,6 @@
                 }
             } else {
                 wct = null;
-                if (mOpeningLeashes != null) {
-                    // TODO: the launcher animation should handle this
-                    for (int i = 0; i < mOpeningLeashes.size(); ++i) {
-                        t.show(mOpeningLeashes.get(i));
-                        t.setAlpha(mOpeningLeashes.get(i), 1.f);
-                    }
-                }
                 if (mPipTask != null && mPipTransaction != null) {
                     t.show(mInfo.getChange(mPipTask).getLeash());
                     PictureInPictureSurfaceTransaction.apply(mPipTransaction,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl
new file mode 100644
index 0000000..366193c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ILauncherUnlockAnimationController.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+// Methods for System UI to interface with Launcher to perform the unlock animation.
+interface ILauncherUnlockAnimationController {
+    // Prepares Launcher for the unlock animation by setting scale/alpha/etc. to their starting
+    // values.
+    void prepareForUnlock(boolean willAnimateSmartspace, int selectedPage);
+
+    // Set the unlock percentage. This is used when System UI is controlling each frame of the
+    // unlock animation, such as during a swipe to unlock touch gesture.
+    oneway void setUnlockAmount(float amount);
+
+    // Play a full unlock animation from 0f to 1f. This is used when System UI is unlocking from a
+    // single action, such as biometric auth, and doesn't need to control individual frames.
+    oneway void playUnlockAnimation(boolean unlocked, long duration);
+
+    // Set the selected page on Launcher's smartspace.
+    oneway void setSmartspaceSelectedPage(int selectedPage);
+
+    // Set the visibility of Launcher's smartspace.
+    void setSmartspaceVisibility(int visibility);
+
+    // Tell SystemUI the smartspace's current state. Launcher code should call this whenever the
+    // smartspace state may have changed.
+    oneway void dispatchSmartspaceStateToSysui();
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
deleted file mode 100644
index 511df4c..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceCallback.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2021 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.shared.system.smartspace;
-
-import com.android.systemui.shared.system.smartspace.SmartspaceState;
-
-// Methods for getting and setting the state of a SmartSpace. This is used to allow a remote process
-// (such as System UI) to sync with and control a SmartSpace view hosted in another process (such as
-// Launcher).
-interface ISmartspaceCallback {
-
-    // Return information about the state of the SmartSpace, including location on-screen and
-    // currently selected page.
-    SmartspaceState getSmartspaceState();
-
-    // Set the currently selected page of this SmartSpace.
-    oneway void setSelectedPage(int selectedPage);
-
-    oneway void setVisibility(int visibility);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
new file mode 100644
index 0000000..cf83f62
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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.shared.system.smartspace;
+
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
+import com.android.systemui.shared.system.smartspace.SmartspaceState;
+
+// System UI unlock controller. Launcher will provide a LauncherUnlockAnimationController to this
+// controller, which System UI will use to control the unlock animation within the Launcher window.
+interface ISysuiUnlockAnimationController {
+    // Provides an implementation of the LauncherUnlockAnimationController to System UI, so that
+    // SysUI can use it to control the unlock animation in the launcher window.
+    oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback);
+
+    // Called by Launcher whenever anything happens to change the state of its smartspace. System UI
+    // proactively saves this and uses it to perform the unlock animation without needing to make a
+    // blocking query to Launcher asking about the smartspace state.
+    oneway void onLauncherSmartspaceStateUpdated(in SmartspaceState state);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index 2d51c4d..d7e61d6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -28,15 +28,18 @@
 class SmartspaceState() : Parcelable {
     var boundsOnScreen: Rect = Rect()
     var selectedPage = 0
+    var visibleOnScreen = false
 
     constructor(parcel: Parcel) : this() {
         this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
         this.selectedPage = parcel.readInt()
+        this.visibleOnScreen = parcel.readBoolean()
     }
 
     override fun writeToParcel(dest: Parcel?, flags: Int) {
         dest?.writeParcelable(boundsOnScreen, 0)
         dest?.writeInt(selectedPage)
+        dest?.writeBoolean(visibleOnScreen)
     }
 
     override fun describeContents(): Int {
@@ -44,7 +47,9 @@
     }
 
     override fun toString(): String {
-        return "boundsOnScreen: $boundsOnScreen, selectedPage: $selectedPage"
+        return "boundsOnScreen: $boundsOnScreen, " +
+                "selectedPage: $selectedPage, " +
+                "visibleOnScreen: $visibleOnScreen"
     }
 
     companion object CREATOR : Parcelable.Creator<SmartspaceState> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 25b5511..8eb5289 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 
 import android.app.WallpaperManager;
 import android.content.res.Resources;
@@ -41,7 +42,6 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -86,6 +86,9 @@
     private AnimatableClockController mLargeClockViewController;
     private FrameLayout mLargeClockFrame; // centered clock
 
+    @KeyguardClockSwitch.ClockSize
+    private int mCurrentClockSize = SMALL;
+
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardBypassController mBypassController;
 
@@ -110,7 +113,6 @@
     private View mSmartspaceView;
 
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    private SmartspaceTransitionController mSmartspaceTransitionController;
 
     private boolean mOnlyClock = false;
     private Executor mUiExecutor;
@@ -136,7 +138,6 @@
             KeyguardBypassController bypassController,
             LockscreenSmartspaceController smartspaceController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            SmartspaceTransitionController smartspaceTransitionController,
             SecureSettings secureSettings,
             @Main Executor uiExecutor,
             @Main Resources resources) {
@@ -155,7 +156,22 @@
         mSecureSettings = secureSettings;
         mUiExecutor = uiExecutor;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
-        mSmartspaceTransitionController = smartspaceTransitionController;
+        mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
+                new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
+                    @Override
+                    public void onSmartspaceSharedElementTransitionStarted() {
+                        // The smartspace needs to be able to translate out of bounds in order to
+                        // end up where the launcher's smartspace is, while its container is being
+                        // swiped off the top of the screen.
+                        setClipChildrenForUnlock(false);
+                    }
+
+                    @Override
+                    public void onUnlockAnimationFinished() {
+                        // For performance reasons, reset this once the unlock animation ends.
+                        setClipChildrenForUnlock(true);
+                    }
+                });
     }
 
     /**
@@ -236,7 +252,7 @@
             mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
 
             updateClockLayout();
-            mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
+            mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
         }
 
         mSecureSettings.registerContentObserver(
@@ -293,6 +309,8 @@
             return;
         }
 
+        mCurrentClockSize = clockSize;
+
         boolean appeared = mView.switchToClock(clockSize, animate);
         if (animate && appeared && clockSize == LARGE) {
             mLargeClockViewController.animateAppear();
@@ -368,7 +386,14 @@
         final Set<View> excludedViews = new HashSet<>();
 
         if (mSmartspaceView != null) {
-            excludedViews.add(mSmartspaceView);
+            excludedViews.add(mStatusArea);
+        }
+
+        // Don't change the alpha of the invisible clock.
+        if (mCurrentClockSize == LARGE) {
+            excludedViews.add(mClockFrame);
+        } else {
+            excludedViews.add(mLargeClockFrame);
         }
 
         setChildrenAlphaExcluding(alpha, excludedViews);
@@ -449,4 +474,16 @@
             mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
         }
     }
+
+    /**
+     * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
+     * bounds during the unlock transition.
+     */
+    private void setClipChildrenForUnlock(boolean clip) {
+        mView.setClipChildren(clip);
+
+        if (mStatusArea != null) {
+            mStatusArea.setClipChildren(clip);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8bf890d..a5fe0ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -22,7 +22,6 @@
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.systemui.communal.CommunalStateController;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -55,7 +54,6 @@
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final KeyguardStateController mKeyguardStateController;
-    private SmartspaceTransitionController mSmartspaceTransitionController;
     private final Rect mClipBounds = new Rect();
 
     @Inject
@@ -69,7 +67,6 @@
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
-            SmartspaceTransitionController smartspaceTransitionController,
             ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
@@ -82,7 +79,6 @@
                 keyguardStateController, dozeParameters, screenOffAnimationController,
                 /* animateYPos= */ true, /* visibleOnCommunal= */ false);
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
-        mSmartspaceTransitionController = smartspaceTransitionController;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index ae0702c..caf0307 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -59,6 +59,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -91,6 +92,7 @@
     private final WindowManager.LayoutParams mWindowLayoutParams;
     private final PhoneWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
+    private final AccessibilityManager mAccessibilityManager;
 
     private final DraggableConstraintLayout mView;
     private final ImageView mImagePreview;
@@ -98,6 +100,7 @@
     private final ScreenshotActionChip mEditChip;
     private final ScreenshotActionChip mRemoteCopyChip;
     private final View mActionContainerBackground;
+    private final View mDismissButton;
 
     private Runnable mOnSessionCompleteListener;
 
@@ -113,6 +116,8 @@
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
 
+        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+
         mWindowManager = mContext.getSystemService(WindowManager.class);
 
         mTimeoutHandler = timeoutHandler;
@@ -135,10 +140,13 @@
         mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
         mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
         mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
+        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
 
         mView.setOnDismissCallback(this::hideImmediate);
         mView.setOnInteractionCallback(() -> mTimeoutHandler.resetTimeout());
 
+        mDismissButton.setOnClickListener(view -> animateOut());
+
         mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
         mRemoteCopyChip.setIcon(
                 Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
@@ -158,10 +166,10 @@
         withWindowAttached(() -> {
             mWindow.setContentView(mView);
             updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
-            getEnterAnimation().start();
+            mView.post(() -> getEnterAnimation().start());
         });
 
-        mTimeoutHandler.setOnTimeoutRunnable(() -> animateOut());
+        mTimeoutHandler.setOnTimeoutRunnable(this::animateOut);
 
         mCloseDialogsReceiver = new BroadcastReceiver() {
             @Override
@@ -226,8 +234,8 @@
                         mView.getLocationOnScreen(pt);
                         Rect rect = new Rect(pt[0], pt[1], pt[0] + mView.getWidth(),
                                 pt[1] + mView.getHeight());
-                        if (!rect.contains((int) motionEvent.getRawX(),
-                                (int) motionEvent.getRawY())) {
+                        if (!rect.contains(
+                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                             animateOut();
                         }
                     }
@@ -311,11 +319,15 @@
         ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
 
         mView.setAlpha(0);
+        mDismissButton.setVisibility(View.GONE);
         final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
         final View actionBackground = requireNonNull(
                 mView.findViewById(R.id.actions_container_background));
         mImagePreview.setVisibility(View.VISIBLE);
         mActionContainerBackground.setVisibility(View.VISIBLE);
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
 
         anim.addUpdateListener(animation -> {
             mView.setAlpha(animation.getAnimatedFraction());
@@ -385,7 +397,7 @@
 
     private void reset() {
         mView.setTranslationX(0);
-        mView.setAlpha(1);
+        mView.setAlpha(0);
         mTimeoutHandler.cancelTimeout();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 9bc3f17..b96c5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,7 +48,6 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.settings.dagger.SettingsModule;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -188,12 +187,6 @@
         return SystemUIFactory.getInstance();
     }
 
-    @SysUISingleton
-    @Provides
-    static SmartspaceTransitionController provideSmartspaceTransitionController() {
-        return new SmartspaceTransitionController();
-    }
-
     // TODO: This should provided by the WM component
     /** Provides Optional of BubbleManager */
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
deleted file mode 100644
index 7c3152f..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHost.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 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.dreams;
-
-import android.view.View;
-
-/**
- * A collection of interfaces related to hosting a complication.
- */
-public abstract class ComplicationHost {
-    /**
-     * An interface for the callback from the complication provider to indicate when the
-     * complication is ready.
-     */
-    public interface CreationCallback {
-        /**
-         * Called to inform the complication view is ready to be placed within the visual space.
-         * @param view The view representing the complication.
-         * @param layoutParams The parameters to create the view with.
-         */
-        void onCreated(View view, ComplicationHostView.LayoutParams layoutParams);
-    }
-
-    /**
-     * An interface for the callback from the complication provider to signal interactions in the
-     * complication.
-     */
-    public interface InteractionCallback {
-        /**
-         * Called to signal the calling complication would like to exit the dream.
-         */
-        void onExit();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
deleted file mode 100644
index a67dd5c..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationHostView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 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.dreams;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-/**
- * {@link ComplicationHostView} is the container view for housing complications above of a dream.
- */
-public class ComplicationHostView extends ConstraintLayout {
-    public ComplicationHostView(Context context) {
-        super(context, null);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs) {
-        super(context, attrs, 0);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, 0);
-    }
-
-    public ComplicationHostView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
deleted file mode 100644
index 099e379..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 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.dreams;
-
-import android.content.Context;
-
-/**
- * {@link ComplicationProvider} is an interface for defining entities that can supply complications
- * to show over a dream. Presentation components such as the {@link DreamOverlayService} supply
- * implementations with the necessary context for constructing such overlays.
- */
-public interface ComplicationProvider {
-    /**
-     * Called when the {@link ComplicationHost} requests the associated complication be produced.
-     *
-     * @param context The {@link Context} used to construct the view.
-     * @param creationCallback The callback to inform the complication has been created.
-     * @param interactionCallback The callback to inform the complication has been interacted with.
-     */
-    void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback,
-            ComplicationHost.InteractionCallback interactionCallback);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5b46079..be76e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -25,11 +25,11 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
 import com.android.systemui.util.ViewController;
@@ -47,6 +47,8 @@
     private final int mDreamOverlayNotificationsDragAreaHeight;
     private final DreamOverlayStatusBarViewController mStatusBarViewController;
 
+    private final ComplicationHostViewController mComplicationHostViewController;
+
     // The dream overlay's content view, which is located below the status bar (in z-order) and is
     // the space into which widgets are placed.
     private final ViewGroup mDreamOverlayContentView;
@@ -74,6 +76,13 @@
                     final int childCount = mDreamOverlayContentView.getChildCount();
                     for (int i = 0; i < childCount; i++) {
                         View child = mDreamOverlayContentView.getChildAt(i);
+
+                        if (mComplicationHostViewController.getView() == child) {
+                            region.op(mComplicationHostViewController.getTouchRegions(),
+                                    Region.Op.UNION);
+                            continue;
+                        }
+
                         if (child.getGlobalVisibleRect(rect)) {
                             region.op(rect, Region.Op.UNION);
                         }
@@ -93,6 +102,7 @@
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
+            ComplicationHostViewComponent.Factory complicationHostViewFactory,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
             DreamOverlayStatusBarViewController statusBarViewController,
             @Main Handler handler,
@@ -106,6 +116,13 @@
                 mView.getResources().getDimensionPixelSize(
                         R.dimen.dream_overlay_notifications_drag_area_height);
 
+        mComplicationHostViewController = complicationHostViewFactory.create().getController();
+        final View view = mComplicationHostViewController.getView();
+
+        mDreamOverlayContentView.addView(view,
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT));
+
         mHandler = handler;
         mMaxBurnInOffset = maxBurnInOffset;
         mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
@@ -114,6 +131,7 @@
     @Override
     protected void onInit() {
         mStatusBarViewController.init();
+        mComplicationHostViewController.init();
     }
 
     @Override
@@ -130,18 +148,10 @@
                 .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
     }
 
-    void addOverlay(View overlayView, ConstraintLayout.LayoutParams layoutParams) {
-        mDreamOverlayContentView.addView(overlayView, layoutParams);
-    }
-
     View getContainerView() {
         return mView;
     }
 
-    void removeAllOverlays() {
-        mDreamOverlayContentView.removeAllViews();
-    }
-
     @VisibleForTesting
     int getDreamOverlayNotificationsDragAreaHeight() {
         return mDreamOverlayNotificationsDragAreaHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a53120f..16ed1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -24,10 +24,13 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 
 import java.util.concurrent.Executor;
@@ -47,8 +50,6 @@
     private final Context mContext;
     // The Executor ensures actions and ui updates happen on the same thread.
     private final Executor mExecutor;
-    // The state controller informs the service of updates to the complications present.
-    private final DreamOverlayStateController mStateController;
     // A controller for the dream overlay container view (which contains both the status bar and the
     // content area).
     private final DreamOverlayContainerViewController mDreamOverlayContainerViewController;
@@ -56,47 +57,51 @@
     // A reference to the {@link Window} used to hold the dream overlay.
     private Window mWindow;
 
-    private final DreamOverlayStateController.Callback mOverlayStateCallback =
-            new DreamOverlayStateController.Callback() {
-                @Override
-                public void onComplicationsChanged() {
-                    mExecutor.execute(() -> reloadComplicationsLocked());
-                }
-            };
+    private final Complication.Host mHost = new Complication.Host() {
+        @Override
+        public void requestExitDream() {
+            mExecutor.execute(DreamOverlayService.this::requestExit);
+        }
+    };
+
+    private final LifecycleRegistry mLifecycleRegistry;
+
+    private ViewModelStore mViewModelStore = new ViewModelStore();
 
     @Inject
     public DreamOverlayService(
             Context context,
             @Main Executor executor,
-            DreamOverlayStateController overlayStateController,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
         mContext = context;
         mExecutor = executor;
-        mStateController = overlayStateController;
-        mDreamOverlayContainerViewController =
-                dreamOverlayComponentFactory.create().getDreamOverlayContainerViewController();
 
-        mStateController.addCallback(mOverlayStateCallback);
+        final DreamOverlayComponent component =
+                dreamOverlayComponentFactory.create(mViewModelStore, mHost);
+        mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController();
+        setCurrentState(Lifecycle.State.CREATED);
+        mLifecycleRegistry = component.getLifecycleRegistry();
+    }
+
+    private void setCurrentState(Lifecycle.State state) {
+        mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
     }
 
     @Override
     public void onDestroy() {
+        setCurrentState(Lifecycle.State.DESTROYED);
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.removeView(mWindow.getDecorView());
-        mStateController.removeCallback(mOverlayStateCallback);
         super.onDestroy();
     }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
-    }
-
-    private void reloadComplicationsLocked() {
-        mDreamOverlayContainerViewController.removeAllOverlays();
-        for (ComplicationProvider overlayProvider : mStateController.getComplications()) {
-            addComplication(overlayProvider);
-        }
+        setCurrentState(Lifecycle.State.STARTED);
+        mExecutor.execute(() -> {
+            addOverlayWindowLocked(layoutParams);
+            setCurrentState(Lifecycle.State.RESUMED);
+        });
     }
 
     /**
@@ -129,20 +134,5 @@
 
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
         windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
-        mExecutor.execute(this::reloadComplicationsLocked);
-    }
-
-    @VisibleForTesting
-    protected void addComplication(ComplicationProvider provider) {
-        provider.onCreateComplication(mContext,
-                (view, layoutParams) -> {
-                    // Always move UI related work to the main thread.
-                    mExecutor.execute(() -> mDreamOverlayContainerViewController
-                            .addOverlay(view, layoutParams));
-                },
-                () -> {
-                    // The Callback is set on the main thread.
-                    mExecutor.execute(this::requestExit);
-                });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 66679bb..e838848 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -17,18 +17,17 @@
 package com.android.systemui.dreams;
 
 import androidx.annotation.NonNull;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.statusbar.policy.CallbackController;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -42,35 +41,6 @@
 @SysUISingleton
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
-    // A counter for guaranteeing unique complications tokens within the scope of this state
-    // controller.
-    private int mNextComplicationTokenId = 0;
-
-    /**
-     * {@link ComplicationToken} provides a unique key for identifying {@link ComplicationProvider}
-     * instances registered with {@link DreamOverlayStateController}.
-     */
-    public static class ComplicationToken {
-        private final int mId;
-
-        private ComplicationToken(int id) {
-            mId = id;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof ComplicationToken)) return false;
-            ComplicationToken that = (ComplicationToken) o;
-            return mId == that.mId;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mId);
-        }
-    }
-
     /**
      * Callback for dream overlay events.
      */
@@ -84,7 +54,8 @@
 
     private final Executor mExecutor;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-    private final HashMap<ComplicationToken, ComplicationProvider> mComplications = new HashMap<>();
+
+    private final Collection<Complication> mComplications = new HashSet();
 
     @VisibleForTesting
     @Inject
@@ -93,47 +64,32 @@
     }
 
     /**
-     * Adds a complication to be presented on top of dreams.
-     * @param provider The {@link ComplicationProvider} providing the dream.
-     * @return The {@link ComplicationToken} tied to the supplied {@link ComplicationProvider}.
+     * Adds a complication to be included on the dream overlay.
      */
-    public ListenableFuture<ComplicationToken> addComplication(ComplicationProvider provider) {
-        return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
-                final ComplicationToken token = new ComplicationToken(mNextComplicationTokenId++);
-                mComplications.put(token, provider);
-                notifyCallbacks();
-                completer.set(token);
-            });
-            return "DreamOverlayStateController::addComplication";
+    public void addComplication(Complication complication) {
+        mExecutor.execute(() -> {
+            if (mComplications.add(complication)) {
+                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+            }
         });
     }
 
     /**
-     * Removes a complication from being shown on dreams.
-     * @param token The {@link ComplicationToken} associated with the {@link ComplicationProvider}
-     *              to be removed.
-     * @return The removed {@link ComplicationProvider}, {@code null} if not found.
+     * Removes a complication from inclusion on the dream overlay.
      */
-    public ListenableFuture<ComplicationProvider> removeComplication(ComplicationToken token) {
-        return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
-                final ComplicationProvider removedComplication = mComplications.remove(token);
-
-                if (removedComplication != null) {
-                    notifyCallbacks();
-                }
-                completer.set(removedComplication);
-            });
-
-            return "DreamOverlayStateController::removeComplication";
+    public void removeComplication(Complication complication) {
+        mExecutor.execute(() -> {
+            if (mComplications.remove(complication)) {
+                mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
+            }
         });
     }
 
-    private void notifyCallbacks() {
-        for (Callback callback : mCallbacks) {
-            callback.onComplicationsChanged();
-        }
+    /**
+     * Returns collection of present {@link Complication}.
+     */
+    public Collection<Complication> getComplications() {
+        return Collections.unmodifiableCollection(mComplications);
     }
 
     @Override
@@ -161,12 +117,4 @@
             mCallbacks.remove(callback);
         });
     }
-
-    /**
-     * Returns all registered {@link ComplicationProvider} instances.
-     * @return A collection of {@link ComplicationProvider}.
-     */
-    public Collection<ComplicationProvider> getComplications() {
-        return mComplications.values();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
new file mode 100644
index 0000000..96cf50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link Complication} is an interface for defining a complication, a visual component rendered
+ * above a dream. {@link Complication} instances encapsulate the logic for generating the view to be
+ * shown, along with the supporting control/logic. The decision for including the
+ * {@link Complication} is not the responsibility of the {@link Complication}. This is instead
+ * handled by domain logic and invocations to add and remove the {@link Complication} through
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#addComplication(Complication)} and
+ * {@link com.android.systemui.dreams.DreamOverlayStateController#removeComplication(Complication)}.
+ * A {@link Complication} also does not represent a specific instance of the view. Instead, it
+ * should be viewed as a provider, where view instances are requested from it. The associated
+ * {@link ViewHolder} interface is requested for each view request. This object is retained for the
+ * view's lifetime, providing a container for any associated logic. The complication rendering
+ * system will consult this {@link ViewHolder} for {@link View} to show and
+ * {@link ComplicationLayoutParams} to position the view. {@link ComplicationLayoutParams} allow for
+ * specifying the sizing and position of the {@link Complication}.
+ *
+ * The following code sample exhibits the entities and lifecycle involved with a
+ * {@link Complication}.
+ *
+ * <pre>{@code
+ * // This component allows for the complication to generate a new ViewHolder for every request.
+ * @Subcomponent
+ * interface ExampleViewHolderComponent {
+ *     @Subcomponent.Factory
+ *     interface Factory {
+ *         ExampleViewHolderComponent create();
+ *     }
+ *
+ *     ExampleViewHolder getViewHolder();
+ * }
+ *
+ * // An example entity that controls whether or not a complication should be included on dreams.
+ * // Note how the complication is tracked by reference for removal.
+ * public class ExampleComplicationProvider {
+ *     private final DreamOverlayStateController mDreamOverlayStateController;
+ *     private final ExampleComplication mComplication;
+ *     @Inject
+ *     public ExampleComplicationProvider(
+ *             ExampleComplication complication,
+ *             DreamOverlayStateController stateController) {
+ *         mDreamOverlayStateController = stateController;
+ *         mComplication = complication;
+ *     }
+ *
+ *     public void onShowConditionsMet(boolean met) {
+ *         if (met) {
+ *             mDreamOverlayStateController.addComplication(mComplication);
+ *         } else {
+ *             mDreamOverlayStateController.removeComplication(mComplication);
+ *         }
+ *     }
+ * }
+ *
+ * // An example complication. Note how a factory is created to supply a unique ViewHolder for each
+ * // request. Also, there is no particular view instance members defined in the complication.
+ * class ExampleComplication implements Complication {
+ *     private final ExampleViewHolderComponent.Factory mFactory;
+ *     @Inject
+ *     public ExampleComplication(ExampleViewHolderComponent.Factory viewHolderComponentFactory) {
+ *         mFactory = viewHolderComponentFactory;
+ *     }
+ *
+ *     @Override
+ *     public ViewHolder createView(ComplicationViewModel model) {
+ *         return mFactory.create().getViewHolder();
+ *     }
+ * }
+ *
+ * // Not every ViewHolder needs to include a view controller. It is included here as an example of
+ * // how such logic can be contained and associated with the ViewHolder lifecycle.
+ * class ExampleViewController extends ViewController<FrameLayout> {
+ *     protected ExampleViewController(FrameLayout view) {
+ *         super(view);
+ *     }
+ *
+ *     @Override
+ *     protected void onViewAttached() { }
+ *
+ *     @Override
+ *     protected void onViewDetached() { }
+ * }
+ *
+ * // An example ViewHolder. This is the correct place to contain any value/logic associated with a
+ * // particular instance of the ComplicationView.
+ * class ExampleViewHolder implements Complication.ViewHolder {
+ *     final FrameLayout mView;
+ *     final ExampleViewController mController;
+ *
+ *     @Inject
+ *     public ExampleViewHolder(Context context) {
+ *         mView = new FrameLayout(context);
+ *         mController = new ExampleViewController(mView);
+ *     }
+ *     @Override
+ *     public View getView() {
+ *         return mView;
+ *     }
+ *
+ *     @Override
+ *     public ComplicationLayoutParams getLayoutParams() {
+ *         return new ComplicationLayoutParams(
+ *                 200,
+ *                 100,
+ *                 ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.DIRECTION_END,
+ *                 ComplicationLayoutParams.DIRECTION_DOWN,
+ *                 4);
+ *     }
+ * }
+ * }
+ * </pre>
+ */
+public interface Complication {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "CATEGORY_" }, value = {
+            CATEGORY_STANDARD,
+            CATEGORY_SYSTEM,
+    })
+
+    @interface Category {}
+    /**
+     * {@code CATEGORY_STANDARD} indicates the complication is a normal component. Rules and
+     * settings, such as hiding all complications, will apply to this complication.
+     */
+    int CATEGORY_STANDARD = 1 << 0;
+    /**
+     * {@code CATEGORY_SYSTEM} indicates complications driven by SystemUI. Usually, these are
+     * core components that are not user controlled. These can potentially deviate from given
+     * rule sets that would normally apply to {@code CATEGORY_STANDARD}.
+     */
+    int CATEGORY_SYSTEM = 1 << 1;
+
+    /**
+     * The {@link Host} interface specifies a way a {@link Complication} to communicate with its
+     * parent entity for information and actions.
+     */
+    interface Host {
+        /**
+         * Called to signal a {@link Complication} has requested to exit the dream.
+         */
+        void requestExitDream();
+    }
+
+    /**
+     * Returned through {@link Complication#createView(ComplicationViewModel)}, {@link ViewHolder}
+     * is a container for a single {@link Complication} instance. The {@link Host} guarantees that
+     * the {@link ViewHolder} will be retained for the lifetime of the {@link Complication}
+     * instance's user. The view is responsible for providing the view that represents the
+     * {@link Complication}. This object is the proper place to store any related entities, such as
+     * a {@link com.android.systemui.util.ViewController} for the view.
+     */
+    interface ViewHolder {
+        /**
+         * Returns the {@link View} associated with the {@link ViewHolder}. This {@link View} should
+         * be stable and generated once.
+         * @return
+         */
+        View getView();
+
+        /**
+         * Returns the {@link Category} associated with the {@link Complication}. {@link Category}
+         * is a grouping which helps define the relationship of the {@link Complication} to
+         * System UI and the rest of the system. It is used for presentation and other decisions.
+         */
+        @Complication.Category
+        default int getCategory() {
+            return Complication.CATEGORY_STANDARD;
+        }
+
+        /**
+         * Returns the {@link ComplicationLayoutParams} associated with this complication. The
+         * values expressed here are treated as preference rather than requirement. The hosting
+         * entity is free to modify/interpret the parameters as deemed fit.
+         */
+        ComplicationLayoutParams getLayoutParams();
+    }
+
+    /**
+     * Generates a {@link ViewHolder} for the {@link Complication}. This captures both the view and
+     * control logic for a single instance of the complication. The {@link Complication} may be
+     * asked at any time to generate another view.
+     * @param model The {@link ComplicationViewModel} associated with this particular
+     *              {@link Complication} instance.
+     * @return a {@link ViewHolder} for this {@link Complication} instance.
+     */
+    ViewHolder createView(ComplicationViewModel model);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
new file mode 100644
index 0000000..76818fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionLiveData} wraps
+ * {@link DreamOverlayStateController#getComplications()} to provide an observable
+ * {@link Complication} data set tied to a lifecycle. This should not be directly accessed. Instead,
+ * clients should access the data from {@link ComplicationCollectionViewModel}.
+ */
+public class ComplicationCollectionLiveData extends LiveData<Collection<Complication>> {
+    final DreamOverlayStateController mDreamOverlayStateController;
+
+    final DreamOverlayStateController.Callback mStateControllerCallback;
+
+    {
+        mStateControllerCallback = new DreamOverlayStateController.Callback() {
+            @Override
+            public void onComplicationsChanged() {
+                setValue(mDreamOverlayStateController.getComplications());
+            }
+
+        };
+    }
+
+    @Inject
+    public ComplicationCollectionLiveData(DreamOverlayStateController stateController) {
+        super();
+        mDreamOverlayStateController = stateController;
+    }
+
+    @Override
+    protected void onActive() {
+        super.onActive();
+        mDreamOverlayStateController.addCallback(mStateControllerCallback);
+        setValue(mDreamOverlayStateController.getComplications());
+    }
+
+    @Override
+    protected void onInactive() {
+        mDreamOverlayStateController.removeCallback(mStateControllerCallback);
+        super.onInactive();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
new file mode 100644
index 0000000..7190d7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionViewModel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Transformations;
+import androidx.lifecycle.ViewModel;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationCollectionViewModel} is an abstraction for observing and accessing
+ * {@link ComplicationViewModel} for registered {@link Complication}.
+ */
+public class ComplicationCollectionViewModel extends ViewModel {
+    private final LiveData<Collection<ComplicationViewModel>> mComplications;
+    private final ComplicationViewModelTransformer mTransformer;
+
+    /**
+     * Injectable constructor for {@link ComplicationCollectionViewModel}. Note that this cannot
+     * be implicitly injected. Clients must bind scoped instance values through the corresponding
+     * dagger subcomponent.
+     */
+    @Inject
+    public ComplicationCollectionViewModel(
+            ComplicationCollectionLiveData complications,
+            ComplicationViewModelTransformer transformer) {
+        mComplications = Transformations.map(complications, collection -> convert(collection));
+        mTransformer = transformer;
+    }
+
+    private Collection<ComplicationViewModel> convert(Collection<Complication> complications) {
+        return complications
+                .stream()
+                .map(complication -> mTransformer.getViewModel(complication))
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Returns {@link LiveData} for the collection of {@link Complication} represented as
+     * {@link ComplicationViewModel}.
+     */
+    public LiveData<Collection<ComplicationViewModel>> getComplications() {
+        return mComplications;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
new file mode 100644
index 0000000..f627f15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+import static com.android.systemui.dreams.complication.dagger.ComplicationModule.SCOPED_COMPLICATIONS_MODEL;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.systemui.util.ViewController;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * The {@link ComplicationHostViewController} is responsible for displaying complications within
+ * a given container. It monitors the available {@link Complication} instances from
+ * {@link com.android.systemui.dreams.DreamOverlayStateController} and inserts/removes them through
+ * a {@link ComplicationLayoutEngine}.
+ */
+public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
+    private final ComplicationLayoutEngine mLayoutEngine;
+    private final LifecycleOwner mLifecycleOwner;
+    private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
+    private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+
+    @Inject
+    protected ComplicationHostViewController(
+            @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
+            ComplicationLayoutEngine layoutEngine,
+            LifecycleOwner lifecycleOwner,
+            @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+        super(view);
+        mLayoutEngine = layoutEngine;
+        mLifecycleOwner = lifecycleOwner;
+        mComplicationCollectionViewModel = viewModel;
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mComplicationCollectionViewModel.getComplications().observe(mLifecycleOwner,
+                complicationViewModels -> updateComplications(complicationViewModels));
+    }
+
+    /**
+     * Returns the region in display space occupied by complications. Touches in this region
+     * (composed of a collection of individual rectangular regions) should be directed to the
+     * complications rather than the region underneath.
+     */
+    public Region getTouchRegions() {
+        final Region region = new Region();
+        final Rect rect = new Rect();
+        final int childCount = mView.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mView.getChildAt(i);
+            if (child.getGlobalVisibleRect(rect)) {
+                region.op(rect, Region.Op.UNION);
+            }
+        }
+
+        return region;
+    }
+
+    private void updateComplications(Collection<ComplicationViewModel> complications) {
+        final Collection<ComplicationId> ids = complications.stream()
+                .map(complicationViewModel -> complicationViewModel.getId())
+                .collect(Collectors.toSet());
+
+        final Collection<ComplicationId> removedComplicationIds =
+                mComplications.keySet().stream()
+                        .filter(complicationId -> !ids.contains(complicationId))
+                        .collect(Collectors.toSet());
+
+        // Trim removed complications
+        removedComplicationIds.forEach(complicationId -> {
+            mLayoutEngine.removeComplication(complicationId);
+            mComplications.remove(complicationId);
+        });
+
+        // Add new complications
+        final Collection<ComplicationViewModel> newComplications = complications
+                .stream()
+                .filter(complication -> !mComplications.containsKey(complication.getId()))
+                .collect(Collectors.toSet());
+
+        newComplications
+                .forEach(complication -> {
+                    final ComplicationId id = complication.getId();
+                    final Complication.ViewHolder viewHolder = complication.getComplication()
+                            .createView(complication);
+                    mComplications.put(id, viewHolder);
+                    mLayoutEngine.addComplication(id, viewHolder.getView(),
+                            viewHolder.getLayoutParams(), viewHolder.getCategory());
+                });
+    }
+
+    @Override
+    protected void onViewAttached() {
+    }
+
+    @Override
+    protected void onViewDetached() {
+    }
+
+    /**
+     * Exposes the associated {@link View}. Since this {@link View} is instantiated through dagger
+     * in the {@link ComplicationHostViewController} constructor, the
+     * {@link ComplicationHostViewController} is responsible for surfacing it so that it can be
+     * included in the parent view hierarchy.
+     */
+    public View getView() {
+        return mView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
new file mode 100644
index 0000000..420359c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationId.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+/**
+ * A {@link ComplicationId} is a value to uniquely identify a complication during the current
+ * runtime and within a particular scope. Any guarantees beyond this will need to be enforced
+ * externally.
+ */
+public class ComplicationId {
+    /**
+     * An associated factory for minting ids that are unique in for the factory's scope.
+     */
+    public static class Factory {
+        private int mNextId;
+
+        ComplicationId getNextId() {
+            return new ComplicationId(mNextId++);
+        }
+    }
+
+    private int mId;
+
+    private ComplicationId(int id) {
+        mId = id;
+    }
+
+    @Override
+    public String toString() {
+        return "ComplicationId{" + "mId=" + mId + "}";
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
new file mode 100644
index 0000000..cb24ae6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent.SCOPED_COMPLICATIONS_LAYOUT;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.Constraints;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ComplicationLayoutEngine} arranges a collection of {@link ComplicationViewModel} based on
+ * their layout parameters and attributes. The management of this set is done by
+ * {@link ComplicationHostViewController}.
+ */
+public class ComplicationLayoutEngine  {
+    public static final String TAG = "ComplicationLayoutEngine";
+
+    /**
+     * {@link ViewEntry} is an internal container, capturing information necessary for working with
+     * a particular {@link Complication} view.
+     */
+    private static class ViewEntry implements Comparable<ViewEntry> {
+        private final View mView;
+        private final ComplicationLayoutParams mLayoutParams;
+        private final Parent mParent;
+        @Complication.Category
+        private final int mCategory;
+
+        /**
+         * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
+         * view hierarchy to be accessed without traversing the entire view tree.
+         */
+        ViewEntry(View view, ComplicationLayoutParams layoutParams, int category, Parent parent) {
+            mView = view;
+            // Views that are generated programmatically do not have a unique id assigned to them
+            // at construction. A new id is assigned here to enable ConstraintLayout relative
+            // specifications. Existing ids for inflated views are not preserved.
+            // {@link Complication.ViewHolder} should not reference the root container by id.
+            mView.setId(View.generateViewId());
+            mLayoutParams = layoutParams;
+            mCategory = category;
+            mParent = parent;
+        }
+
+        /**
+         * Returns the {@link View} associated with the {@link Complication}. This is the instance
+         * passed in at construction. The reference to this {@link View} is captured when the
+         * {@link Complication} is added to the {@link ComplicationLayoutEngine}. The
+         * {@link Complication} cannot modify the {@link View} reference beyond this point.
+         */
+        private View getView() {
+            return mView;
+        }
+
+        /**
+         * Returns The {@link ComplicationLayoutParams} associated with the view.
+         */
+        public ComplicationLayoutParams getLayoutParams() {
+            return mLayoutParams;
+        }
+
+        /**
+         * Interprets the {@link #getLayoutParams()} into {@link ConstraintLayout.LayoutParams} and
+         * applies them to the view. The method accounts for the relationship of the {@link View} to
+         * the other {@link Complication} views around it. The organization of the {@link View}
+         * instances in {@link ComplicationLayoutEngine} can be seen as lists. A {@link View} is
+         * either the head of its list or a following node. This head is passed into this method,
+         * which can be a reference to the {@link View} to indicate it is the head.
+         */
+        public void applyLayoutParams(View head) {
+            // Only the basic dimension parameters from the base ViewGroup.LayoutParams are carried
+            // over verbatim from the complication specified LayoutParam. Other fields are
+            // interpreted.
+            final ConstraintLayout.LayoutParams params =
+                    new Constraints.LayoutParams(mLayoutParams.width, mLayoutParams.height);
+
+            final int direction = getLayoutParams().getDirection();
+
+            // If no parent, view is the anchor. In this case, it is given the highest priority for
+            // alignment. All alignment preferences are done in relation to the parent container.
+            final boolean isRoot = head == mView;
+
+            // Each view can be seen as a vector, having a point (described here as position) and
+            // direction. When a view is the head of a position, then it is the first in a sequence
+            // of complications to appear from that position. For example, being the head for
+            // position POSITION_TOP | POSITION_END will cause the view to be shown as the first
+            // view in that corner. In this case, the positions specify which sides to align with
+            // the parent. If the view is not the head, the positions perpendicular to the direction
+            // of the view specify which side to align with the opposing side of the head view.
+            // Otherwise, the position aligns with the containing view. This means a
+            // POSITION_BOTTOM | POSITION_START with DIRECTION_UP non-head view's bottom to be
+            // aligned with the preceding view node's top and start to be aligned with the
+            // parent's start.
+            mLayoutParams.iteratePositions(position -> {
+                switch(position) {
+                    case ComplicationLayoutParams.POSITION_START:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_END) {
+                            params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.startToEnd = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_TOP:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) {
+                            params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.topToBottom = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_BOTTOM:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) {
+                            params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.bottomToTop = head.getId();
+                        }
+                        break;
+                    case ComplicationLayoutParams.POSITION_END:
+                        if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) {
+                            params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
+                        } else {
+                            params.endToStart = head.getId();
+                        }
+                        break;
+                }
+            });
+
+            mView.setLayoutParams(params);
+        }
+
+        /**
+         * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from
+         * being shown further.
+         */
+        public void remove() {
+            mParent.removeEntry(this);
+
+            ((ViewGroup) mView.getParent()).removeView(mView);
+        }
+
+        @Override
+        public int compareTo(ViewEntry viewEntry) {
+            // If the two entries have different categories, system complications take precedence.
+            if (viewEntry.mCategory != mCategory) {
+                // Note that this logic will need to be adjusted if more categories are introduced.
+                return mCategory == Complication.CATEGORY_SYSTEM ? 1 : -1;
+            }
+
+            // A higher weight indicates greater precedence if all else being equal.
+            if (viewEntry.mLayoutParams.getWeight() != mLayoutParams.getWeight()) {
+                return mLayoutParams.getWeight() > viewEntry.mLayoutParams.getWeight() ? 1 : -1;
+            }
+
+            return 0;
+        }
+
+        /**
+         * {@link Builder} allows for a multiple entities to contribute to the {@link ViewEntry}
+         * construction. This is necessary for setting an immutable parent, which might not be
+         * known until the view hierarchy is traversed.
+         */
+        private static class Builder {
+            private final View mView;
+            private final ComplicationLayoutParams mLayoutParams;
+            private final int mCategory;
+            private Parent mParent;
+
+            Builder(View view, ComplicationLayoutParams lp, @Complication.Category int category) {
+                mView = view;
+                mLayoutParams = lp;
+                mCategory = category;
+            }
+
+            /**
+             * Returns the set {@link ComplicationLayoutParams}
+             */
+            public ComplicationLayoutParams getLayoutParams() {
+                return mLayoutParams;
+            }
+
+            /**
+             * Returns the set {@link Complication.Category}.
+             */
+            @Complication.Category
+            public int getCategory() {
+                return mCategory;
+            }
+
+            /**
+             * Sets the parent. Note that this references to the entity for handling events, such as
+             * requesting the removal of the {@link View}. It is not the
+             * {@link android.view.ViewGroup} which contains the {@link View}.
+             */
+            Builder setParent(Parent parent) {
+                mParent = parent;
+                return this;
+            }
+
+            /**
+             * Builds and returns the resulting {@link ViewEntry}.
+             */
+            ViewEntry build() {
+                return new ViewEntry(mView, mLayoutParams, mCategory, mParent);
+            }
+        }
+
+        /**
+         * An interface allowing an {@link ViewEntry} to signal events.
+         */
+        interface Parent {
+            /**
+             * Indicates the {@link ViewEntry} requests removal.
+             */
+            void removeEntry(ViewEntry entry);
+        }
+    }
+
+    /**
+     * {@link PositionGroup} represents a collection of {@link Complication} at a given location.
+     * It further organizes the {@link Complication} by the direction in which they emanate from
+     * this position.
+     */
+    private static class PositionGroup implements DirectionGroup.Parent {
+        private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>();
+
+        /**
+         * Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to
+         * this group. It is assumed that the caller has correctly identified this
+         * {@link PositionGroup} as the proper home for the {@link Complication} based on its
+         * declared position.
+         */
+        public ViewEntry add(ViewEntry.Builder entryBuilder) {
+            final int direction = entryBuilder.getLayoutParams().getDirection();
+            if (!mDirectionGroups.containsKey(direction)) {
+                mDirectionGroups.put(direction, new DirectionGroup(this));
+            }
+
+            return mDirectionGroups.get(direction).add(entryBuilder);
+        }
+
+        @Override
+        public void onEntriesChanged() {
+            // Whenever an entry is added/removed from a child {@link DirectionGroup}, it is vital
+            // that all {@link DirectionGroup} children are visited. It is possible the overall
+            // head has changed, requiring constraints to be adjusted.
+            updateViews();
+        }
+
+        private void updateViews() {
+            ViewEntry head = null;
+
+            // Identify which {@link Complication} head from the set of {@link DirectionGroup}
+            // should be treated as the {@link PositionGroup} head.
+            for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+                final ViewEntry groupHead = directionGroup.getHead();
+                if (head == null || (groupHead != null && groupHead.compareTo(head) > 0)) {
+                    head = groupHead;
+                }
+            }
+
+            // A headless position group indicates no complications.
+            if (head == null) {
+                return;
+            }
+
+            for (DirectionGroup directionGroup : mDirectionGroups.values()) {
+                // Tell each {@link DirectionGroup} to update its containing {@link ViewEntry} based
+                // on the identified head. This iteration will also capture any newly added views.
+                directionGroup.updateViews(head.getView());
+            }
+        }
+    }
+
+    /**
+     * A {@link DirectionGroup} organizes the {@link ViewEntry} of a parent group that point are
+     * laid out in the same direction.
+     */
+    private static class DirectionGroup implements ViewEntry.Parent {
+        /**
+         * An interface implemented by the {@link DirectionGroup} parent to receive updates.
+         */
+        interface Parent {
+            /**
+             * Invoked to indicate a change to the {@link ViewEntry} composition for this
+             * {@link DirectionGroup}.
+             */
+            void onEntriesChanged();
+        }
+        private final ArrayList<ViewEntry> mViews = new ArrayList<>();
+        private final Parent mParent;
+
+        /**
+         * Creates a new {@link DirectionGroup} with the specified parent. Note that the
+         * {@link DirectionGroup} does not store its own direction. It is the responsibility of the
+         * {@link DirectionGroup.Parent} to maintain this association.
+         */
+        DirectionGroup(Parent parent) {
+            mParent = parent;
+        }
+
+        /**
+         * Returns the head of the group. It is assumed that the order of the {@link ViewEntry} is
+         * proactively maintained.
+         */
+        public ViewEntry getHead() {
+            return mViews.isEmpty() ? null : mViews.get(0);
+        }
+
+        /**
+         * Adds a {@link ViewEntry} via {@link ViewEntry.Builder} to this group.
+         */
+        public ViewEntry add(ViewEntry.Builder entryBuilder) {
+            final ViewEntry entry = entryBuilder.setParent(this).build();
+            mViews.add(entry);
+
+            // After adding view, reverse sort collection.
+            Collections.sort(mViews);
+            Collections.reverse(mViews);
+
+            mParent.onEntriesChanged();
+
+            return entry;
+        }
+
+        @Override
+        public void removeEntry(ViewEntry entry) {
+            // Sort is handled when the view is added, so should still be correct after removal.
+            // However, the head may have been removed, which may affect the layout of views in
+            // other DirectionGroups of the same PositionGroup.
+            mViews.remove(entry);
+            mParent.onEntriesChanged();
+        }
+
+        /**
+         * Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with
+         * the specified head. Note that the head might not be in this group and instead part of a
+         * neighboring group.
+         */
+        public void updateViews(View groupHead) {
+            Iterator<ViewEntry> it = mViews.iterator();
+
+            while (it.hasNext()) {
+                final ViewEntry viewEntry = it.next();
+                viewEntry.applyLayoutParams(groupHead);
+                groupHead = viewEntry.getView();
+            }
+        }
+    }
+
+    private final ConstraintLayout mLayout;
+    private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
+    private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
+
+    /** */
+    @Inject
+    public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout) {
+        mLayout = layout;
+    }
+
+    /**
+     * Adds a complication to this {@link ComplicationLayoutEngine}.
+     * @param id A {@link ComplicationId} unique to this complication. If this matches a
+     *           complication within this {@link ComplicationViewModel}, the existing complication
+     *           will be removed.
+     * @param view The {@link View} to be shown.
+     * @param lp The {@link ComplicationLayoutParams} as expressed by the {@link Complication}.
+     *           These will be interpreted into the final applied parameters.
+     * @param category The {@link Complication.Category} for the {@link Complication}.
+     */
+    public void addComplication(ComplicationId id, View view,
+            ComplicationLayoutParams lp, @Complication.Category int category) {
+        // If the complication is present, remove.
+        if (mEntries.containsKey(id)) {
+            removeComplication(id);
+        }
+
+        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, lp, category);
+
+        // Add position group if doesn't already exist
+        final int position = lp.getPosition();
+        if (!mPositions.containsKey(position)) {
+            mPositions.put(position, new PositionGroup());
+        }
+
+        // Insert entry into group
+        final ViewEntry entry = mPositions.get(position).add(entryBuilder);
+        mEntries.put(id, entry);
+
+        mLayout.addView(entry.getView());
+    }
+
+    /**
+     * Removes a complication by {@link ComplicationId}.
+     */
+    public void removeComplication(ComplicationId id) {
+        if (!mEntries.containsKey(id)) {
+            Log.e(TAG, "could not find id:" + id);
+            return;
+        }
+
+        final ViewEntry entry = mEntries.get(id);
+        entry.remove();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
new file mode 100644
index 0000000..f9a69fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import android.annotation.IntDef;
+import android.view.ViewGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * {@link ComplicationLayoutParams} allows a {@link Complication} to express its preferred location
+ * and dimensions. Note that these parameters are not directly applied by any {@link ViewGroup}.
+ * They are instead consulted for the final parameters which best seem fit for usage.
+ */
+public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "POSITION_" }, value = {
+            POSITION_TOP,
+            POSITION_END,
+            POSITION_BOTTOM,
+            POSITION_START,
+    })
+
+    @interface Position {}
+    /** Align view with the top of parent or bottom of preceding {@link Complication}. */
+    static final int POSITION_TOP = 1 << 0;
+    /** Align view with the bottom of parent or top of preceding {@link Complication}. */
+    static final int POSITION_BOTTOM = 1 << 1;
+    /** Align view with the start of parent or end of preceding {@link Complication}. */
+    static final int POSITION_START = 1 << 2;
+    /** Align view with the end of parent or start of preceding {@link Complication}. */
+    static final int POSITION_END = 1 << 3;
+
+    private static final int FIRST_POSITION = POSITION_TOP;
+    private static final int LAST_POSITION = POSITION_END;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
+            DIRECTION_UP,
+            DIRECTION_DOWN,
+            DIRECTION_START,
+            DIRECTION_END,
+    })
+
+    @interface Direction {}
+    /** Position view upward from position. */
+    static final int DIRECTION_UP = 1 << 0;
+    /** Position view downward from position. */
+    static final int DIRECTION_DOWN = 1 << 1;
+    /** Position view towards the start of the parent. */
+    static final int DIRECTION_START = 1 << 2;
+    /** Position view towards the end of parent. */
+    static final int DIRECTION_END = 1 << 3;
+
+    @Position
+    private final int mPosition;
+
+    @Direction
+    private final int mDirection;
+
+    private final int mWeight;
+
+    // Do not allow specifying opposite positions
+    private static final int[] INVALID_POSITIONS =
+            { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
+
+    // Do not allow for specifying a direction towards the outside of the container.
+    private static final Map<Integer, Integer> INVALID_DIRECTIONS;
+    static {
+        INVALID_DIRECTIONS = new HashMap<>();
+        INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
+        INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
+        INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
+        INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight) {
+        super(width, height);
+
+        if (!validatePosition(position)) {
+            throw new IllegalArgumentException("invalid position:" + position);
+        }
+        mPosition = position;
+
+        if (!validateDirection(position, direction)) {
+            throw new IllegalArgumentException("invalid direction:" + direction);
+        }
+
+        mDirection = direction;
+
+        mWeight = weight;
+    }
+
+    /**
+     * Constructs {@link ComplicationLayoutParams} from an existing instance.
+     */
+    public ComplicationLayoutParams(ComplicationLayoutParams source) {
+        super(source);
+        mPosition = source.mPosition;
+        mDirection = source.mDirection;
+        mWeight = source.mWeight;
+    }
+
+    private static boolean validateDirection(@Position int position, @Direction int direction) {
+        for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+                currentPosition <<= 1) {
+            if ((position & currentPosition) == currentPosition
+                    && INVALID_DIRECTIONS.containsKey(currentPosition)
+                    && (direction & INVALID_DIRECTIONS.get(currentPosition)) != 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Iterates over the defined positions and invokes the specified {@link Consumer} for each
+     * position specified for this {@link ComplicationLayoutParams}.
+     */
+    public void iteratePositions(Consumer<Integer> consumer) {
+        for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
+                currentPosition <<= 1) {
+            if ((mPosition & currentPosition) == currentPosition) {
+                consumer.accept(currentPosition);
+            }
+        }
+    }
+
+    private static boolean validatePosition(@Position int position) {
+        if (position == 0) {
+            return false;
+        }
+
+        for (int combination : INVALID_POSITIONS) {
+            if ((position & combination) == combination) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Direction
+    public int getDirection() {
+        return mDirection;
+    }
+
+    @Position
+    public int getPosition() {
+        return mPosition;
+    }
+
+    public int getWeight() {
+        return mWeight;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
new file mode 100644
index 0000000..f023937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import androidx.lifecycle.ViewModel;
+
+import javax.inject.Inject;
+
+/**
+ * {@link ComplicationViewModel} is an abstraction over {@link Complication}, providing the model
+ * from which any view-related interpretation of the {@link Complication} should be derived from.
+ */
+public class ComplicationViewModel extends ViewModel {
+    private final Complication mComplication;
+    private final ComplicationId mId;
+    private final Complication.Host mHost;
+
+    /**
+     * Default constructor for generating a {@link ComplicationViewModel}.
+     * @param complication The {@link Complication} represented by the view model.
+     * @param id The {@link ComplicationId} tied to this {@link Complication}.
+     * @param host The environment {@link Complication.Host}.
+     */
+    @Inject
+    public ComplicationViewModel(Complication complication, ComplicationId id,
+            Complication.Host host) {
+        mComplication = complication;
+        mId = id;
+        mHost = host;
+    }
+
+    /**
+     * Returns the {@link ComplicationId} for this {@link Complication} for stable id association.
+     */
+    public ComplicationId getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the underlying {@link Complication}. Should only as a redirection - for example,
+     * using the {@link Complication} to generate view. Any property should be surfaced through
+     * this ViewModel.
+     */
+    public Complication getComplication() {
+        return mComplication;
+    }
+
+    /**
+     * Requests the dream exit on behalf of the {@link Complication}.
+     */
+    public void exitDream() {
+        mHost.requestExitDream();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
new file mode 100644
index 0000000..cc17ea1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.dagger.DaggerViewModelProviderFactory;
+
+import javax.inject.Inject;
+
+/**
+ * An intermediary to generate {@link ComplicationViewModel} tracked with a {@link ViewModelStore}.
+ */
+public class ComplicationViewModelProvider extends ViewModelProvider {
+    @Inject
+    public ComplicationViewModelProvider(ViewModelStore store, ComplicationViewModel viewModel) {
+        super(store, new DaggerViewModelProviderFactory(() -> viewModel));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
new file mode 100644
index 0000000..5d113dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link ComplicationViewModelTransformer} responsibility is provide a mapping from
+ * {@link Complication} to {@link ComplicationViewModel}.
+ */
+public class ComplicationViewModelTransformer {
+    private final ComplicationId.Factory mComplicationIdFactory = new ComplicationId.Factory();
+    private final HashMap<Complication, ComplicationId> mComplicationIdMapping = new HashMap<>();
+    private final ComplicationViewModelComponent.Factory mViewModelComponentFactory;
+
+    @Inject
+    public ComplicationViewModelTransformer(
+            ComplicationViewModelComponent.Factory viewModelComponentFactory) {
+        mViewModelComponentFactory = viewModelComponentFactory;
+    }
+
+    /**
+     * Generates {@link ComplicationViewModel} from a {@link Complication}.
+     */
+    public ComplicationViewModel getViewModel(Complication complication) {
+        final ComplicationId id = getComplicationId(complication);
+        return mViewModelComponentFactory.create(complication, id)
+                .getViewModelProvider().get(id.toString(), ComplicationViewModel.class);
+    }
+
+    private ComplicationId getComplicationId(Complication complication) {
+        if (!mComplicationIdMapping.containsKey(complication)) {
+            mComplicationIdMapping.put(complication, mComplicationIdFactory.getNextId());
+        }
+
+        return mComplicationIdMapping.get(complication);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
new file mode 100644
index 0000000..4cc905e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewComponent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.LayoutInflater;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+
+/**
+ * {@link ComplicationHostViewComponent} encapsulates the shared logic around the host view layer
+ * for complications. Anything that references the layout should be provided through this component
+ * and its child module. The factory should be used in order to best tie the lifetime of the view
+ * to components.
+ */
+@Subcomponent(modules = {
+        ComplicationHostViewComponent.ComplicationHostViewModule.class,
+})
+@ComplicationHostViewComponent.ComplicationHostViewScope
+public interface ComplicationHostViewComponent {
+    String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
+
+    /** Scope annotation for singleton items within {@link ComplicationHostViewComponent}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface ComplicationHostViewScope {}
+
+    /**
+     * Factory for generating a new scoped component.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        ComplicationHostViewComponent create();
+    }
+
+    /** */
+    ComplicationHostViewController getController();
+
+    /**
+     * Module for providing a scoped host view.
+     */
+    @Module
+    abstract class ComplicationHostViewModule {
+        /**
+         * Generates a {@link ConstraintLayout}, which can host
+         * {@link com.android.systemui.dreams.complication.Complication} instances.
+         */
+        @Provides
+        @Named(SCOPED_COMPLICATIONS_LAYOUT)
+        @ComplicationHostViewScope
+        static ConstraintLayout providesComplicationHostView(
+                LayoutInflater layoutInflater) {
+            return Preconditions.checkNotNull((ConstraintLayout)
+                            layoutInflater.inflate(R.layout.dream_overlay_complications_layer,
+                                    null),
+                    "R.layout.dream_overlay_complications_layer did not properly inflated");
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
new file mode 100644
index 0000000..b29e8c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Named;
+import javax.inject.Scope;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module for housing components related to rendering complications.
+ */
+@Module(subcomponents = {
+        ComplicationViewModelComponent.class,
+        ComplicationHostViewComponent.class,
+})
+public interface ComplicationModule {
+    String SCOPED_COMPLICATIONS_MODEL = "scoped_complications_model";
+
+    /** Scope annotation for singleton items within the {@link ComplicationModule}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface ComplicationScope {}
+
+    /**
+     * The complication collection is provided through this way to ensure that the instances are
+     * tied to the {@link ViewModelStore}.
+     */
+    @Provides
+    @Named(SCOPED_COMPLICATIONS_MODEL)
+    static ComplicationCollectionViewModel providesComplicationCollectionViewModel(
+            ViewModelStore store, ComplicationCollectionViewModel viewModel) {
+        final ViewModelProvider provider = new ViewModelProvider(store,
+                new DaggerViewModelProviderFactory(() -> viewModel));
+
+        return provider.get(ComplicationCollectionViewModel.class);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
new file mode 100644
index 0000000..703cd28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationViewModelComponent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication.dagger;
+
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationId;
+import com.android.systemui.dreams.complication.ComplicationViewModelProvider;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * The {@link ComplicationViewModelComponent} allows for a
+ * {@link com.android.systemui.dreams.complication.ComplicationViewModel} for a particular
+ * {@link Complication}. This component binds these instance specific values to allow injection with
+ * values provided at the wider scope.
+ */
+@Subcomponent
+public interface ComplicationViewModelComponent {
+    /**
+     * Factory for generating {@link ComplicationViewModelComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        ComplicationViewModelComponent create(@BindsInstance Complication complication,
+                @BindsInstance ComplicationId id);
+    }
+
+    /** */
+    ComplicationViewModelProvider getViewModelProvider();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
new file mode 100644
index 0000000..8ffedec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DaggerViewModelProviderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication.dagger;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+/**
+ * {@link DaggerViewModelProviderFactory} is a wrapper around a lambda allowing for uses of Dagger
+ * component.
+ */
+public class DaggerViewModelProviderFactory implements ViewModelProvider.Factory {
+    /**
+     * An interface for providing a {@link ViewModel} through
+     * {@link DaggerViewModelProviderFactory}.
+     */
+    public interface ViewModelCreator {
+        /**
+         * Creates a {@link ViewModel} to be returned.
+         */
+        ViewModel create();
+    }
+
+    private final ViewModelCreator mCreator;
+
+    public DaggerViewModelProviderFactory(ViewModelCreator creator) {
+        mCreator = creator;
+    }
+
+    @NonNull
+    @Override
+    public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
+        return (T) mCreator.create();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 072f50d..d5053a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -22,6 +22,7 @@
  * Dagger Module providing Communal-related functionality.
  */
 @Module(subcomponents = {
-        DreamOverlayComponent.class})
+        DreamOverlayComponent.class,
+})
 public interface DreamModule {
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index c90332b..f0ab696 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -18,25 +18,36 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Scope;
 
+import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
  * Dagger subcomponent for {@link DreamOverlayModule}.
  */
-@Subcomponent(modules = {DreamOverlayModule.class})
+@Subcomponent(modules = {
+        DreamOverlayModule.class,
+        ComplicationModule.class,
+})
 @DreamOverlayComponent.DreamOverlayScope
 public interface DreamOverlayComponent {
     /** Simple factory for {@link DreamOverlayComponent}. */
     @Subcomponent.Factory
     interface Factory {
-        DreamOverlayComponent create();
+        DreamOverlayComponent create(@BindsInstance ViewModelStore store,
+                @BindsInstance Complication.Host host);
     }
 
     /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -46,6 +57,11 @@
     @interface DreamOverlayScope {}
 
     /** Builds a {@link DreamOverlayContainerViewController}. */
-    @DreamOverlayScope
     DreamOverlayContainerViewController getDreamOverlayContainerViewController();
+
+    /** Builds a {@link androidx.lifecycle.LifecycleRegistry} */
+    LifecycleRegistry getLifecycleRegistry();
+
+    /** Builds a {@link androidx.lifecycle.LifecycleOwner} */
+    LifecycleOwner getLifecycleOwner();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index d291203..b56aa2c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -22,6 +22,9 @@
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
@@ -36,6 +39,7 @@
 
 import javax.inject.Named;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -124,4 +128,16 @@
         return resources.getInteger(
                 R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis);
     }
+
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
+        return () -> lifecycleRegistryLazy.get();
+    }
+
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    static LifecycleRegistry providesLifecycleRegistry(LifecycleOwner lifecycleOwner) {
+        return new LifecycleRegistry(lifecycleOwner);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 4be819a..e24df30 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -85,7 +85,7 @@
             new BooleanFlag(400, true);
 
     public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
-            new BooleanFlag(401, false);
+            new BooleanFlag(401, true);
 
     public static final ResourceBooleanFlag SMARTSPACE =
             new ResourceBooleanFlag(402, R.bool.flag_smartspace);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 69bcf2e..582965a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -21,6 +21,8 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Handler
 import android.view.RemoteAnimationTarget
 import android.view.SyncRtSurfaceTransactionApplier
 import android.view.View
@@ -33,11 +35,17 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.SmartspaceState
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
 import javax.inject.Inject
+import kotlin.math.min
 
 /**
  * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
@@ -76,6 +84,25 @@
 const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f
 
 /**
+ * How long the canned unlock animation takes. This is used if we are unlocking from biometric auth,
+ * from a tap on the unlock icon, or from the bouncer. This is not relevant if the lockscreen is
+ * swiped away via a touch gesture, or when it's flinging expanded/collapsed after a swipe.
+ */
+const val UNLOCK_ANIMATION_DURATION_MS = 200L
+
+/**
+ * Duration for the alpha animation on the surface behind. This plays to fade in the surface during
+ * a swipe to unlock (and to fade it back out if the swipe is cancelled).
+ */
+const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 150L
+
+/**
+ * Start delay for the surface behind animation, used so that the lockscreen can get out of the way
+ * before the surface begins appearing.
+ */
+const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L
+
+/**
  * Initiates, controls, and ends the keyguard unlock animation.
  *
  * The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface
@@ -85,7 +112,7 @@
  * this controller will play a canned animation on the surface as well.
  *
  * The surface behind the keyguard is manipulated via a RemoteAnimation passed to
- * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator].
+ * [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
  */
 @SysUISingleton
 class KeyguardUnlockAnimationController @Inject constructor(
@@ -94,10 +121,99 @@
     private val
     keyguardViewMediator: Lazy<KeyguardViewMediator>,
     private val keyguardViewController: KeyguardViewController,
-    private val smartspaceTransitionController: SmartspaceTransitionController,
     private val featureFlags: FeatureFlags,
-    private val biometricUnlockController: BiometricUnlockController
-) : KeyguardStateController.Callback {
+    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>
+) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
+
+    interface KeyguardUnlockAnimationListener {
+        /**
+         * Called when the remote unlock animation, controlled by
+         * [KeyguardUnlockAnimationController], first starts.
+         *
+         * [playingCannedAnimation] indicates whether we are playing a canned animation to show the
+         * app/launcher behind the keyguard, vs. this being a swipe to unlock where the dismiss
+         * amount drives the animation.
+         * [fromWakeAndUnlock] tells us whether we are unlocking directly from AOD - in this case,
+         * the lockscreen is dismissed instantly, so we shouldn't run any animations that rely on it
+         * being visible.
+         */
+        @JvmDefault
+        fun onUnlockAnimationStarted(playingCannedAnimation: Boolean, fromWakeAndUnlock: Boolean) {}
+
+        /**
+         * Called when the remote unlock animation ends, in all cases, canned or swipe-to-unlock.
+         * The keyguard is no longer visible in this state and the app/launcher behind the keyguard
+         * is now completely visible.
+         */
+        @JvmDefault
+        fun onUnlockAnimationFinished() {}
+
+        /**
+         * Called when we begin the smartspace shared element transition, either due to an unlock
+         * action (biometric, etc.) or a swipe to unlock.
+         *
+         * This transition can begin BEFORE [onUnlockAnimationStarted] is called, if we are swiping
+         * to unlock and the surface behind the keyguard has not yet been made visible. This is
+         * because the lockscreen smartspace immediately begins moving towards the launcher
+         * smartspace location when a swipe begins, even before we start the keyguard exit remote
+         * animation and show the launcher itself.
+         */
+        @JvmDefault
+        fun onSmartspaceSharedElementTransitionStarted() {}
+    }
+
+    /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
+    var lockscreenSmartspace: View? = null
+
+    /**
+     * The state of the Launcher's smartspace, delivered via [onLauncherSmartspaceStateUpdated].
+     * This is pushed to us from Launcher whenever their smartspace moves or its visibility changes.
+     * We'll animate the lockscreen smartspace to this location during an unlock.
+     */
+    var launcherSmartspaceState: SmartspaceState? = null
+
+    /**
+     * Whether a canned unlock animation is playing, vs. currently unlocking in response to a swipe
+     * gesture or panel fling. If we're swiping/flinging, the unlock animation is driven by the
+     * dismiss amount, via [onKeyguardDismissAmountChanged]. If we're using a canned animation, it's
+     * being driven by ValueAnimators started in [playCannedUnlockAnimation].
+     */
+    var playingCannedUnlockAnimation = false
+
+    /**
+     * Remote callback provided by Launcher that allows us to control the Launcher's unlock
+     * animation and smartspace.
+     *
+     * If this is null, we will not be animating any Launchers today and should fall back to window
+     * animations.
+     */
+    private var launcherUnlockController: ILauncherUnlockAnimationController? = null
+
+    private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
+
+    /**
+     * Called from SystemUiProxy to pass us the launcher's unlock animation controller. If this
+     * doesn't happen, we won't use in-window animations or the smartspace shared element
+     * transition, but that's okay!
+     */
+    override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) {
+        launcherUnlockController = callback
+
+        // If the provided callback dies, set it to null. We'll always check whether it's null
+        // to avoid DeadObjectExceptions.
+        callback?.asBinder()?.linkToDeath({
+            launcherUnlockController = null
+            launcherSmartspaceState = null
+        }, 0 /* flags */)
+    }
+
+    /**
+     * Called from SystemUiProxy to pass us the latest state of the Launcher's smartspace. This is
+     * only done when the state has changed in some way.
+     */
+    override fun onLauncherSmartspaceStateUpdated(state: SmartspaceState?) {
+        launcherSmartspaceState = state
+    }
 
     /**
      * Information used to start, run, and finish a RemoteAnimation on the app or launcher surface
@@ -105,15 +221,16 @@
      *
      * If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the
      * dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed
-     * duration, and it ends when the gesture reaches a certain threshold or is cancelled.
+     * duration, and it ends when the gesture reaches a certain threshold or is cancell
      *
      * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
-     * animation is started in [notifyStartKeyguardExitAnimation].
+     * animation is started in [playCannedUnlockAnimation].
      */
     @VisibleForTesting
     var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
     private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null
     private var surfaceBehindRemoteAnimationStartTime: Long = 0
+    private var surfaceBehindParams: SyncRtSurfaceTransactionApplier.SurfaceParams? = null
 
     /**
      * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -125,6 +242,7 @@
      */
     private var surfaceBehindAlpha = 1f
     private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+    private var smartspaceAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     /**
      * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -144,57 +262,113 @@
     /** Rounded corner radius to apply to the surface behind the keyguard. */
     private var roundedCornerRadius = 0f
 
-    /** The SmartSpace view on the lockscreen, provided by [KeyguardClockSwitchController]. */
-    public var lockscreenSmartSpace: View? = null
-
-    /**
-     * Whether we are currently in the process of unlocking the keyguard, and we are performing the
-     * shared element SmartSpace transition.
-     */
-    private var unlockingWithSmartSpaceTransition: Boolean = false
-
     /**
      * Whether we tried to start the SmartSpace shared element transition for this unlock swipe.
-     * It's possible we're unable to do so (if the Launcher SmartSpace is not available).
+     * It's possible we were unable to do so (if the Launcher SmartSpace is not available), and we
+     * need to keep track of that so that we don't start doing it halfway through the swipe if
+     * Launcher becomes available suddenly.
      */
     private var attemptedSmartSpaceTransitionForThisSwipe = false
 
-    init {
-        surfaceBehindAlphaAnimator.duration = 150
-        surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN
-        surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
-            surfaceBehindAlpha = valueAnimator.animatedValue as Float
-            updateSurfaceBehindAppearAmount()
-        }
-        surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator) {
-                // If the surface alpha is 0f, it's no longer visible so we can safely be done with
-                // the animation.
-                if (surfaceBehindAlpha == 0f) {
-                    keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
-                            false /* cancelled */)
-                }
-            }
-        })
+    /**
+     * The original location of the lockscreen smartspace on the screen.
+     */
+    private val smartspaceOriginBounds = Rect()
 
-        surfaceBehindEntryAnimator.duration = 450
-        surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT
-        surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
-            surfaceBehindAlpha = valueAnimator.animatedValue as Float
-            setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
-        }
-        surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator) {
-                keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
-                        false /* cancelled */)
+    /**
+     * The bounds to which the lockscreen smartspace is moving. This is set to the bounds of the
+     * launcher's smartspace prior to the transition starting.
+     */
+    private val smartspaceDestBounds = Rect()
+
+    /**
+     * From 0f to 1f, the progress of the smartspace shared element animation. 0f means the
+     * smartspace is at its normal position within the lock screen hierarchy, and 1f means it has
+     * fully animated to the location of the Launcher's smartspace.
+     */
+    private var smartspaceUnlockProgress = 0f
+
+    /**
+     * Whether we're currently unlocking, and we're talking to Launcher to perform in-window
+     * animations rather than simply animating the Launcher window like any other app. This can be
+     * true while [unlockingWithSmartspaceTransition] is false, if the smartspace is not available
+     * or was not ready in time.
+     */
+    private var unlockingToLauncherWithInWindowAnimations: Boolean = false
+
+    /**
+     * Whether we are currently unlocking, and the smartspace shared element transition is in
+     * progress. If true, we're also [unlockingToLauncherWithInWindowAnimations].
+     */
+    private var unlockingWithSmartspaceTransition: Boolean = false
+
+    private val handler = Handler()
+
+    init {
+        with(surfaceBehindAlphaAnimator) {
+            duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
+            interpolator = Interpolators.TOUCH_RESPONSE
+            addUpdateListener { valueAnimator: ValueAnimator ->
+                surfaceBehindAlpha = valueAnimator.animatedValue as Float
+                updateSurfaceBehindAppearAmount()
             }
-        })
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    // If the surface alpha is 0f, it's no longer visible so we can safely be done
+                    // with the animation even if other properties are still animating.
+                    if (surfaceBehindAlpha == 0f) {
+                        keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
+                            false /* cancelled */)
+                    }
+                }
+            })
+        }
+
+        with(surfaceBehindEntryAnimator) {
+            duration = UNLOCK_ANIMATION_DURATION_MS
+            startDelay = UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+            interpolator = Interpolators.TOUCH_RESPONSE
+            addUpdateListener { valueAnimator: ValueAnimator ->
+                surfaceBehindAlpha = valueAnimator.animatedValue as Float
+                setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
+            }
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    playingCannedUnlockAnimation = false
+                    keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+                        false /* cancelled */
+                    )
+                }
+            })
+        }
+
+        with(smartspaceAnimator) {
+            duration = UNLOCK_ANIMATION_DURATION_MS
+            interpolator = Interpolators.TOUCH_RESPONSE
+            addUpdateListener {
+                smartspaceUnlockProgress = it.animatedValue as Float
+            }
+            addListener(object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
+                    keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+                        false /* cancelled */)
+                }
+            })
+        }
 
         // Listen for changes in the dismiss amount.
         keyguardStateController.addCallback(this)
 
         roundedCornerRadius =
-                context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+            context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
+    }
+
+    /**
+     * Add a listener to be notified of various stages of the unlock animation.
+     */
+    fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
+        listeners.add(listener)
     }
 
     /**
@@ -203,78 +377,220 @@
      * surface for the unlock gesture/animation.
      *
      * When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]
-     * to end the RemoteAnimation.
+     * to end the RemoteAnimation. The KeyguardViewMediator will then end the animation and let us
+     * know that it's over by calling [notifyFinishedKeyguardExitAnimation].
      *
-     * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a
+     * [requestedShowSurfaceBehindKeyguard] indicates whether the animation started because of a
      * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture,
-     * as opposed to the keyguard hiding.
+     * as opposed to being called because the device was unlocked and the keyguard is going away.
      */
-    fun notifyStartKeyguardExitAnimation(
+    fun notifyStartSurfaceBehindRemoteAnimation(
         target: RemoteAnimationTarget,
         startTime: Long,
         requestedShowSurfaceBehindKeyguard: Boolean
     ) {
-
         if (surfaceTransactionApplier == null) {
             surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
                     keyguardViewController.viewRootImpl.view)
         }
 
+        // New animation, new params.
+        surfaceBehindParams = null
+
         surfaceBehindRemoteAnimationTarget = target
         surfaceBehindRemoteAnimationStartTime = startTime
 
-        // If the surface behind wasn't made visible during a swipe, we'll do a canned animation
-        // to animate it in. Otherwise, the swipe touch events will continue animating it.
+        // If we specifically requested that the surface behind be made visible, it means we are
+        // swiping to unlock. In that case, the surface visibility is tied to the dismiss amount,
+        // and we'll handle that in onKeyguardDismissAmountChanged(). If we didn't request that, the
+        // keyguard is being dismissed for a different reason (biometric auth, etc.) and we should
+        // play a canned animation to make the surface fully visible.
         if (!requestedShowSurfaceBehindKeyguard) {
-            keyguardViewController.hide(startTime, 350)
-
-            // If we're wake and unlocking, we don't want to animate the surface since we're going
-            // to do the light reveal scrim from the black AOD screen. Make it visible and end the
-            // remote aimation.
-            if (biometricUnlockController.isWakeAndUnlock) {
-                setSurfaceBehindAppearAmount(1f)
-                keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
-                    false /* cancelled */)
-            } else {
-                // Otherwise, animate it in normally.
-                surfaceBehindEntryAnimator.start()
-            }
+            playCannedUnlockAnimation()
         }
 
+        listeners.forEach {
+            it.onUnlockAnimationStarted(
+                playingCannedUnlockAnimation /* playingCannedAnimation */,
+                biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */) }
+
         // Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
         // Check it here in case there is no more change to the dismiss amount after the last change
         // that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached()
         finishKeyguardExitRemoteAnimationIfReachThreshold()
     }
 
-    fun notifyCancelKeyguardExitAnimation() {
-        surfaceBehindRemoteAnimationTarget = null
-    }
+    /**
+     * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
+     * we should clean up all of our state.
+     */
+    fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
+        // Cancel any pending actions.
+        handler.removeCallbacksAndMessages(null)
 
-    fun notifyFinishedKeyguardExitAnimation() {
-        surfaceBehindRemoteAnimationTarget = null
-    }
+        // Make sure we made the surface behind fully visible, just in case. It should already be
+        // fully visible.
+        setSurfaceBehindAppearAmount(1f)
+        launcherUnlockController?.setUnlockAmount(1f)
+        smartspaceDestBounds.setEmpty()
 
-    fun hideKeyguardViewAfterRemoteAnimation() {
-        keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
+        // That target is no longer valid since the animation finished, null it out.
+        surfaceBehindRemoteAnimationTarget = null
+        surfaceBehindParams = null
+
+        playingCannedUnlockAnimation = false
+        unlockingToLauncherWithInWindowAnimations = false
+        unlockingWithSmartspaceTransition = false
+        resetSmartspaceTransition()
+
+        listeners.forEach { it.onUnlockAnimationFinished() }
     }
 
     /**
-     * Whether we are currently in the process of unlocking the keyguard, and we are performing the
-     * shared element SmartSpace transition.
+     * Play a canned unlock animation to unlock the device. This is used when we were *not* swiping
+     * to unlock using a touch gesture. If we were swiping to unlock, the animation will be driven
+     * by the dismiss amount via [onKeyguardDismissAmountChanged].
      */
-    fun isUnlockingWithSmartSpaceTransition(): Boolean {
-        return unlockingWithSmartSpaceTransition
+    fun playCannedUnlockAnimation() {
+        playingCannedUnlockAnimation = true
+
+        if (canPerformInWindowLauncherAnimations()) {
+            // If possible, use the neat in-window animations to unlock to the launcher.
+            unlockToLauncherWithInWindowAnimations()
+        } else if (!biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+            // If the launcher isn't behind the keyguard, or the launcher unlock controller is not
+            // available, animate in the entire window.
+            surfaceBehindEntryAnimator.start()
+        } else {
+            setSurfaceBehindAppearAmount(1f)
+            keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false)
+        }
+
+        // If this is a wake and unlock, hide the lockscreen immediately. In the future, we should
+        // animate it out nicely instead, but to the current state of wake and unlock, not hiding it
+        // causes a lot of issues.
+        // TODO(b/210016643): Not this, it looks not-ideal!
+        if (biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+            keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
+        }
+    }
+
+    /**
+     * Unlock to the launcher, using in-window animations, and the smartspace shared element
+     * transition if possible.
+     */
+    private fun unlockToLauncherWithInWindowAnimations() {
+        unlockingToLauncherWithInWindowAnimations = true
+
+        // See if we can do the smartspace transition, and if so, do it!
+        if (prepareForSmartspaceTransition()) {
+            animateSmartspaceToDestination()
+            listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
+        }
+
+        // Tell the launcher to prepare for the animation by setting its views invisible and
+        // syncing the selected smartspace pages.
+        launcherUnlockController?.prepareForUnlock(
+            unlockingWithSmartspaceTransition /* willAnimateSmartspace */,
+            (lockscreenSmartspace as BcSmartspaceDataPlugin.SmartspaceView?)?.selectedPage ?: -1)
+
+        // Begin the animation.
+        launcherUnlockController?.playUnlockAnimation(
+            true /* unlocked */, UNLOCK_ANIMATION_DURATION_MS)
+        if (!unlockingWithSmartspaceTransition) {
+            // If we are not unlocking with the smartspace transition, wait for the unlock animation
+            // to end and then finish the remote animation. If we are using the smartspace
+            // transition, it will finish the remote animation once it ends.
+            handler.postDelayed({
+                keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+                    false /* cancelled */)
+            }, UNLOCK_ANIMATION_DURATION_MS)
+        }
+
+        // Wait a moment, then show the launcher surface.
+        setSurfaceBehindAppearAmount(1f)
+    }
+
+    /**
+     * Animates the lockscreen smartspace all the way to the launcher's smartspace location, then
+     * makes the launcher smartspace visible and ends the remote animation.
+     */
+    private fun animateSmartspaceToDestination() {
+        smartspaceAnimator.start()
+    }
+
+    /**
+     * Reset the lockscreen smartspace's position, and reset all state involving the smartspace
+     * transition.
+     */
+    public fun resetSmartspaceTransition() {
+        unlockingWithSmartspaceTransition = false
+        smartspaceUnlockProgress = 0f
+
+        lockscreenSmartspace?.post {
+            lockscreenSmartspace!!.translationX = 0f
+            lockscreenSmartspace!!.translationY = 0f
+        }
+    }
+
+    /**
+     * Moves the lockscreen smartspace towards the launcher smartspace's position.
+     */
+    private fun setSmartspaceProgressToDestinationBounds(progress: Float) {
+        if (smartspaceDestBounds.isEmpty) {
+            return
+        }
+
+        val progressClamped = min(1f, progress)
+
+        // Calculate the distance (relative to the origin) that we need to be for the current
+        // progress value.
+        val progressX =
+                (smartspaceDestBounds.left - smartspaceOriginBounds.left) * progressClamped
+        val progressY =
+                (smartspaceDestBounds.top - smartspaceOriginBounds.top) * progressClamped
+
+        val lockscreenSmartspaceCurrentBounds = Rect().also {
+            lockscreenSmartspace!!.getBoundsOnScreen(it)
+        }
+
+        // Figure out how far that is from our present location on the screen. This approach
+        // compensates for the fact that our parent container is also translating to animate out.
+        val dx = smartspaceOriginBounds.left + progressX -
+                lockscreenSmartspaceCurrentBounds.left
+        val dy = smartspaceOriginBounds.top + progressY -
+                lockscreenSmartspaceCurrentBounds.top
+
+        with(lockscreenSmartspace!!) {
+            translationX += dx
+            translationY += dy
+        }
     }
 
     /**
      * Update the lockscreen SmartSpace to be positioned according to the current dismiss amount. As
      * the dismiss amount increases, we will increase our SmartSpace's progress to the destination
      * bounds (the location of the Launcher SmartSpace).
+     *
+     * This is used by [KeyguardClockSwitchController] to keep the smartspace position updated as
+     * the clock is swiped away.
      */
     fun updateLockscreenSmartSpacePosition() {
-        smartspaceTransitionController.setProgressToDestinationBounds(
-                keyguardStateController.dismissAmount / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+        setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
+    }
+
+    /**
+     * Asks the keyguard view to hide, using the start time from the beginning of the remote
+     * animation.
+     */
+    fun hideKeyguardViewAfterRemoteAnimation() {
+        // Hide the keyguard, with no fade out since we animated it away during the unlock.
+        keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 0 /* fadeOutDuration */)
+    }
+
+    private fun applyParamsToSurface(params: SyncRtSurfaceTransactionApplier.SurfaceParams) {
+        surfaceTransactionApplier!!.scheduleApply(params)
+        surfaceBehindParams = params
     }
 
     /**
@@ -287,34 +603,56 @@
             return
         }
 
-        val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
-        val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                MathUtils.clamp(amount, 0f, 1f))
+        if (unlockingToLauncherWithInWindowAnimations) {
+            // If we're using the in-window launcher animations, and haven't yet applied alpha = 1f
+            // to the launcher surface, do that now so we can see the launcher animations.
+            if (surfaceBehindParams?.alpha?.let { it < 1f } != false) {
+                applyParamsToSurface(
+                    SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                        surfaceBehindRemoteAnimationTarget!!.leash)
+                    .withAlpha(1f)
+                    .build())
+            }
 
-        // Scale up from a point at the center-bottom of the surface.
-        surfaceBehindMatrix.setScale(
+            // If we aren't using the canned unlock animation (which would be setting the unlock
+            // amount in its update listener), do it here.
+            if (!isPlayingCannedUnlockAnimation()) {
+                launcherUnlockController?.setUnlockAmount(amount)
+            }
+        } else {
+            // Otherwise, animate in the surface's scale/transltion.
+            val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
+            val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+                    (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+                    MathUtils.clamp(amount, 0f, 1f))
+
+            // Scale up from a point at the center-bottom of the surface.
+            surfaceBehindMatrix.setScale(
                 scaleFactor,
                 scaleFactor,
                 surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f,
-                surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y)
+                surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+            )
 
-        // Translate up from the bottom.
-        surfaceBehindMatrix.postTranslate(0f,
-                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount))
+            // Translate up from the bottom.
+            surfaceBehindMatrix.postTranslate(
+                0f,
+                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+            )
 
-        // If we're snapping the keyguard back, immediately begin fading it out.
-        val animationAlpha =
+            // If we're snapping the keyguard back, immediately begin fading it out.
+            val animationAlpha =
                 if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
                 else surfaceBehindAlpha
 
-        val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                surfaceBehindRemoteAnimationTarget!!.leash)
+            applyParamsToSurface(
+                SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                    surfaceBehindRemoteAnimationTarget!!.leash)
                 .withMatrix(surfaceBehindMatrix)
                 .withCornerRadius(roundedCornerRadius)
                 .withAlpha(animationAlpha)
-                .build()
-        surfaceTransactionApplier!!.scheduleApply(params)
+                .build())
+        }
     }
 
     /**
@@ -326,6 +664,10 @@
             return
         }
 
+        if (playingCannedUnlockAnimation) {
+            return
+        }
+
         // For fling animations, we want to animate the surface in over the full distance. If we're
         // dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to
         // bring in the surface behind over a relatively short swipe distance (~15%), to keep the
@@ -344,17 +686,19 @@
     }
 
     override fun onKeyguardDismissAmountChanged() {
-        if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
+        if (!willHandleUnlockAnimation()) {
             return
         }
 
         if (keyguardViewController.isShowing) {
-            updateKeyguardViewMediatorIfThresholdsReached()
+            showOrHideSurfaceIfDismissAmountThresholdsReached()
 
             // If the surface is visible or it's about to be, start updating its appearance to
             // reflect the new dismiss amount.
-            if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
-                    keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+            if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+                    keyguardViewMediator.get()
+                        .isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
+                    !playingCannedUnlockAnimation) {
                 updateSurfaceBehindAppearAmount()
             }
         }
@@ -362,7 +706,7 @@
         // The end of the SmartSpace transition can occur after the keyguard is hidden (when we tell
         // Launcher's SmartSpace to become visible again), so update it even if the keyguard view is
         // no longer showing.
-        updateSmartSpaceTransition()
+        applyDismissAmountToSmartspaceTransition()
     }
 
     /**
@@ -370,16 +714,28 @@
      * such as reaching the point in the dismiss swipe where we need to make the surface behind the
      * keyguard visible.
      */
-    private fun updateKeyguardViewMediatorIfThresholdsReached() {
+    private fun showOrHideSurfaceIfDismissAmountThresholdsReached() {
         if (!featureFlags.isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION)) {
             return
         }
 
+        // If we are playing the canned unlock animation, we flung away the keyguard to hide it and
+        // started a canned animation to show the surface behind the keyguard. The fling will cause
+        // panel height/dismiss amount updates, but we should ignore those updates here since the
+        // surface behind is already visible and animating.
+        if (playingCannedUnlockAnimation) {
+            return
+        }
+
         val dismissAmount = keyguardStateController.dismissAmount
         if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                 !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
             // We passed the threshold, and we're not yet showing the surface behind the
             // keyguard. Animate it in.
+            if (canPerformInWindowLauncherAnimations()) {
+                launcherUnlockController?.setUnlockAmount(0f)
+                unlockingToLauncherWithInWindowAnimations = true
+            }
             keyguardViewMediator.get().showSurfaceBehindKeyguard()
             fadeInSurfaceBehind()
         } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
@@ -388,9 +744,9 @@
             // out.
             keyguardViewMediator.get().hideSurfaceBehindKeyguard()
             fadeOutSurfaceBehind()
-        } else {
-            finishKeyguardExitRemoteAnimationIfReachThreshold()
         }
+
+        finishKeyguardExitRemoteAnimationIfReachThreshold()
     }
 
     /**
@@ -417,6 +773,7 @@
                         // animating it out. This will be called again after the fling ends.
                         !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
                         dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+            setSurfaceBehindAppearAmount(1f)
             keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(false /* cancelled */)
         }
     }
@@ -426,31 +783,53 @@
      * dismiss amount, and also updates the SmartSpaceTransitionController, which will let Launcher
      * know if it needs to do something as a result.
      */
-    private fun updateSmartSpaceTransition() {
+    private fun applyDismissAmountToSmartspaceTransition() {
         if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
             return
         }
 
+        // If we are playing the canned animation, the smartspace is being animated directly between
+        // its original location and the location of the launcher smartspace by smartspaceAnimator.
+        // We can ignore the dismiss amount, which is caused by panel height changes as the panel is
+        // flung away.
+        if (playingCannedUnlockAnimation) {
+            return
+        }
+
         val dismissAmount = keyguardStateController.dismissAmount
 
-        // If we've begun a swipe, and are capable of doing the SmartSpace transition, start it!
+        // If we've begun a swipe, and haven't yet tried doing the SmartSpace transition, do that
+        // now.
         if (!attemptedSmartSpaceTransitionForThisSwipe &&
-                dismissAmount > 0f &&
-                dismissAmount < 1f &&
-                keyguardViewController.isShowing) {
+            keyguardViewController.isShowing &&
+            dismissAmount > 0f &&
+            dismissAmount < 1f) {
             attemptedSmartSpaceTransitionForThisSwipe = true
 
-            smartspaceTransitionController.prepareForUnlockTransition()
-            if (keyguardStateController.canPerformSmartSpaceTransition()) {
-                unlockingWithSmartSpaceTransition = true
-                smartspaceTransitionController.launcherSmartspace?.setVisibility(
-                        View.INVISIBLE)
+            if (prepareForSmartspaceTransition()) {
+                unlockingWithSmartspaceTransition = true
+
+                // Ensure that the smartspace is invisible if we're doing the transition, and
+                // visible if we aren't.
+                launcherUnlockController?.setSmartspaceVisibility(
+                    if (unlockingWithSmartspaceTransition) View.INVISIBLE else View.VISIBLE)
+
+                if (unlockingWithSmartspaceTransition) {
+                    listeners.forEach { it.onSmartspaceSharedElementTransitionStarted() }
+                }
             }
         } else if (attemptedSmartSpaceTransitionForThisSwipe &&
-                (dismissAmount == 0f || dismissAmount == 1f)) {
+            (dismissAmount == 0f || dismissAmount == 1f)) {
             attemptedSmartSpaceTransitionForThisSwipe = false
-            unlockingWithSmartSpaceTransition = false
-            smartspaceTransitionController.launcherSmartspace?.setVisibility(View.VISIBLE)
+            unlockingWithSmartspaceTransition = false
+            launcherUnlockController?.setSmartspaceVisibility(View.VISIBLE)
+        }
+
+        if (unlockingWithSmartspaceTransition) {
+            val swipedFraction: Float = keyguardStateController.dismissAmount
+            val progress = swipedFraction / DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD
+            smartspaceUnlockProgress = progress
+            setSmartspaceProgressToDestinationBounds(smartspaceUnlockProgress)
         }
     }
 
@@ -463,4 +842,121 @@
         surfaceBehindAlphaAnimator.cancel()
         surfaceBehindAlphaAnimator.reverse()
     }
+
+    /**
+     * Prepare for the smartspace shared element transition, if possible, by figuring out where we
+     * are animating from/to.
+     *
+     * Return true if we'll be able to do the smartspace transition, or false if conditions are not
+     * right to do it right now.
+     */
+    private fun prepareForSmartspaceTransition(): Boolean {
+        // Feature is disabled, so we don't want to.
+        if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
+            return false
+        }
+
+        // If our controllers are null, or we haven't received a smartspace state from Launcher yet,
+        // we will not be doing any smartspace transitions today.
+        if (launcherUnlockController == null ||
+            lockscreenSmartspace == null ||
+            launcherSmartspaceState == null) {
+            return false
+        }
+
+        // If the launcher does not have a visible smartspace (either because it's paged off-screen,
+        // or the smartspace just doesn't exist), we can't do the transition.
+        if ((launcherSmartspaceState?.visibleOnScreen) != true) {
+            return false
+        }
+
+        // If our launcher isn't underneath, then we're unlocking to an app or custom launcher,
+        // neither of which have a smartspace.
+        if (!isNexusLauncherUnderneath()) {
+            return false
+        }
+
+        // TODO(b/213910911): Unfortunately the keyguard is hidden instantly on wake and unlock, so
+        // we won't have a lockscreen smartspace to animate. This is sad, and we should fix that!
+        if (biometricUnlockControllerLazy.get().isWakeAndUnlock) {
+            return false
+        }
+
+        // If we can't dismiss the lock screen via a swipe, then the only way we can do the shared
+        // element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
+        // is showing, and you can't see the lockscreen smartspace, so a shared element transition
+        // would not make sense.
+        if (!keyguardStateController.canDismissLockScreen() &&
+            !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+            return false
+        }
+
+        unlockingWithSmartspaceTransition = true
+        smartspaceDestBounds.setEmpty()
+
+        // Assuming we were able to retrieve the launcher's state, start the lockscreen
+        // smartspace at 0, 0, and save its starting bounds.
+        with(lockscreenSmartspace!!) {
+            translationX = 0f
+            translationY = 0f
+            getBoundsOnScreen(smartspaceOriginBounds)
+        }
+
+        // Set the destination bounds to the launcher smartspace's bounds, offset by any
+        // padding on our smartspace.
+        with(smartspaceDestBounds) {
+            set(launcherSmartspaceState!!.boundsOnScreen)
+            offset(-lockscreenSmartspace!!.paddingLeft, -lockscreenSmartspace!!.paddingTop)
+        }
+
+        return true
+    }
+
+    /**
+     * Whether we should be able to do the in-window launcher animations given the current state of
+     * the device.
+     */
+    fun canPerformInWindowLauncherAnimations(): Boolean {
+        return isNexusLauncherUnderneath() && launcherUnlockController != null
+    }
+
+    /**
+     * Whether we are currently in the process of unlocking the keyguard, and we are performing the
+     * shared element SmartSpace transition.
+     */
+    fun isUnlockingWithSmartSpaceTransition(): Boolean {
+        return unlockingWithSmartspaceTransition
+    }
+
+    /**
+     * Whether this animation controller will be handling the unlock. We require remote animations
+     * to be enabled to do this.
+     *
+     * If this is not true, nothing in this class is relevant, and the unlock will be handled in
+     * [KeyguardViewMediator].
+     */
+    fun willHandleUnlockAnimation(): Boolean {
+        return KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation
+    }
+
+    /**
+     * Whether we are playing a canned unlock animation, vs. unlocking from a touch gesture such as
+     * a swipe.
+     */
+    fun isPlayingCannedUnlockAnimation(): Boolean {
+        return playingCannedUnlockAnimation
+    }
+
+    companion object {
+        /**
+         * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
+         * launcher or an app. If so, we can communicate with it to perform in-window/shared element
+         * transitions!
+         */
+        fun isNexusLauncherUnderneath(): Boolean {
+            return ActivityManagerWrapper.getInstance()
+                    .runningTask?.topActivity?.className?.equals(
+                            QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8376681..08e1654 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2235,8 +2235,9 @@
                         createInteractionJankMonitorConf("DismissPanel"));
 
                 // Pass the surface and metadata to the unlock animation controller.
-                mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
-                        apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
+                mKeyguardUnlockAnimationControllerLazy.get()
+                        .notifyStartSurfaceBehindRemoteAnimation(
+                                apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
             } else {
                 mInteractionJankMonitor.begin(
                         createInteractionJankMonitorConf("RemoteAnimationDisabled"));
@@ -2373,8 +2374,10 @@
 
             finishSurfaceBehindRemoteAnimation(cancelled);
             mSurfaceBehindRemoteAnimationRequested = false;
-            mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
         });
+
+        mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation(
+                cancelled);
     }
 
     /**
@@ -2412,8 +2415,16 @@
         return mSurfaceBehindRemoteAnimationRequested;
     }
 
+    public boolean isAnimatingBetweenKeyguardAndSurfaceBehind() {
+        return mSurfaceBehindRemoteAnimationRunning;
+    }
+
     /** If it's running, finishes the RemoteAnimation on the surface behind the keyguard. */
     public void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
+        if (!mSurfaceBehindRemoteAnimationRunning) {
+            return;
+        }
+
         mSurfaceBehindRemoteAnimationRunning = false;
 
         if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index d1fe7d4..4baef3a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -21,8 +21,6 @@
 import android.view.WindowManager;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
@@ -33,10 +31,8 @@
 import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Optional;
-import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -89,14 +85,11 @@
     static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender(
             MediaTttFlags mediaTttFlags,
             Context context,
-            WindowManager windowManager,
-            @Main Executor mainExecutor,
-            @Background Executor backgroundExecutor) {
+            WindowManager windowManager) {
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
-        return Optional.of(new MediaTttChipControllerSender(
-                context, windowManager, mainExecutor, backgroundExecutor));
+        return Optional.of(new MediaTttChipControllerSender(context, windowManager));
     }
 
     /** */
@@ -119,9 +112,7 @@
             MediaTttFlags mediaTttFlags,
             CommandRegistry commandRegistry,
             Context context,
-            MediaTttChipControllerSender mediaTttChipControllerSender,
-            MediaTttChipControllerReceiver mediaTttChipControllerReceiver,
-            @Main DelayableExecutor mainExecutor) {
+            MediaTttChipControllerReceiver mediaTttChipControllerReceiver) {
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
@@ -129,9 +120,7 @@
                 new MediaTttCommandLineHelper(
                         commandRegistry,
                         context,
-                        mediaTttChipControllerSender,
-                        mediaTttChipControllerReceiver,
-                        mainExecutor));
+                        mediaTttChipControllerReceiver));
     }
 
     /** Inject into MediaTttSenderService. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 460d38f..3720851 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -28,21 +28,22 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
-import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
+import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast
 import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast
-import com.android.systemui.media.taptotransfer.sender.TransferInitiated
-import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
+import com.android.systemui.media.taptotransfer.sender.TransferFailed
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded
+import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered
+import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded
 import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.PrintWriter
-import java.util.concurrent.FutureTask
 import javax.inject.Inject
 
 /**
@@ -53,11 +54,9 @@
 class MediaTttCommandLineHelper @Inject constructor(
     commandRegistry: CommandRegistry,
     private val context: Context,
-    private val mediaTttChipControllerSender: MediaTttChipControllerSender,
     private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver,
-    @Main private val mainExecutor: DelayableExecutor,
 ) {
-    private var senderCallback: IDeviceSenderCallback? = null
+    private var senderService: IDeviceSenderService? = null
     private val senderServiceConnection = SenderServiceConnection()
 
     private val appIconDrawable =
@@ -66,17 +65,15 @@
         }
 
     init {
-        commandRegistry.registerCommand(
-            ADD_CHIP_COMMAND_SENDER_TAG) { AddChipCommandSender() }
-        commandRegistry.registerCommand(
-            REMOVE_CHIP_COMMAND_SENDER_TAG) { RemoveChipCommandSender() }
+        commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
         commandRegistry.registerCommand(
             ADD_CHIP_COMMAND_RECEIVER_TAG) { AddChipCommandReceiver() }
         commandRegistry.registerCommand(
             REMOVE_CHIP_COMMAND_RECEIVER_TAG) { RemoveChipCommandReceiver() }
     }
 
-    inner class AddChipCommandSender : Command {
+    /** All commands for the sender device. */
+    inner class SenderCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
             val otherDeviceName = args[0]
             val mediaInfo = MediaRoute2Info.Builder("id", "Test Name")
@@ -86,62 +83,99 @@
 
             when (args[1]) {
                 MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> {
-                    runOnService { senderCallback ->
-                        senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
+                    runOnService { senderService ->
+                        senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
                     }
                 }
-
-                // TODO(b/203800643): Migrate other commands to invoke the service instead of the
-                //   controller.
-                TRANSFER_INITIATED_COMMAND_NAME -> {
-                    val futureTask = FutureTask { fakeUndoRunnable }
-                    mediaTttChipControllerSender.displayChip(
-                        TransferInitiated(
-                            appIconDrawable,
-                            APP_ICON_CONTENT_DESCRIPTION,
-                            otherDeviceName,
-                            futureTask
-                        )
-                    )
-                    mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
-
+                MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> {
+                    runOnService { senderService ->
+                        senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+                    }
                 }
-                TRANSFER_SUCCEEDED_COMMAND_NAME -> {
-                    mediaTttChipControllerSender.displayChip(
-                        TransferSucceeded(
-                            appIconDrawable,
-                            APP_ICON_CONTENT_DESCRIPTION,
-                            otherDeviceName,
-                            fakeUndoRunnable
-                        )
-                    )
+                TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> {
+                    runOnService { senderService ->
+                        senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+                    }
+                }
+                TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> {
+                    runOnService { senderService ->
+                        senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
+                    }
+                }
+                TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> {
+                    val undoCallback = object : IUndoTransferCallback.Stub() {
+                        override fun onUndoTriggered() {
+                            Log.i(TAG, "Undo transfer to receiver callback triggered")
+                            // The external services that implement this callback would kick off a
+                            // transfer back to this device, so mimic that here.
+                            runOnService { senderService ->
+                                senderService
+                                    .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
+                            }
+                        }
+                    }
+                    runOnService { senderService ->
+                        senderService
+                            .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
+                    }
+                }
+                TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> {
+                    val undoCallback = object : IUndoTransferCallback.Stub() {
+                        override fun onUndoTriggered() {
+                            Log.i(TAG, "Undo transfer to this device callback triggered")
+                            // The external services that implement this callback would kick off a
+                            // transfer back to the receiver, so mimic that here.
+                            runOnService { senderService ->
+                                senderService
+                                    .transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+                            }
+                        }
+                    }
+                    runOnService { senderService ->
+                        senderService
+                            .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback)
+                    }
+                }
+                TRANSFER_FAILED_COMMAND_NAME -> {
+                    runOnService { senderService ->
+                        senderService.transferFailed(mediaInfo, otherDeviceInfo)
+                    }
+                }
+                NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> {
+                    runOnService { senderService ->
+                        senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo)
+                        context.unbindService(senderServiceConnection)
+                    }
                 }
                 else -> {
-                    pw.println("Chip type must be one of " +
+                    pw.println("Sender command must be one of " +
                             "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
-                            "$TRANSFER_INITIATED_COMMAND_NAME, " +
-                            TRANSFER_SUCCEEDED_COMMAND_NAME
+                            "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " +
+                            "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " +
+                            "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " +
+                            "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " +
+                            "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " +
+                            "$TRANSFER_FAILED_COMMAND_NAME, " +
+                            NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
                     )
                 }
             }
         }
 
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar " +
-                    "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>"
-            )
+            pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>")
         }
 
-        private fun runOnService(command: SenderCallbackCommand) {
-            val currentServiceCallback = senderCallback
-            if (currentServiceCallback != null) {
-                command.run(currentServiceCallback)
+        private fun runOnService(command: SenderServiceCommand) {
+            val currentService = senderService
+            if (currentService != null) {
+                command.run(currentService)
             } else {
                 bindService(command)
             }
         }
 
-        private fun bindService(command: SenderCallbackCommand) {
+        private fun bindService(command: SenderServiceCommand) {
             senderServiceConnection.pendingCommand = command
             val binding = context.bindService(
                 Intent(context, MediaTttSenderService::class.java),
@@ -152,19 +186,6 @@
         }
     }
 
-    /** A command to REMOVE the media ttt chip on the SENDER device. */
-    inner class RemoveChipCommandSender : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-            mediaTttChipControllerSender.removeChip()
-            if (senderCallback != null) {
-                context.unbindService(senderServiceConnection)
-            }
-        }
-        override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG")
-        }
-    }
-
     /** A command to DISPLAY the media ttt chip on the RECEIVER device. */
     inner class AddChipCommandReceiver : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
@@ -187,36 +208,32 @@
         }
     }
 
-    /** A service connection for [IDeviceSenderCallback]. */
+    /** A service connection for [IDeviceSenderService]. */
     private inner class SenderServiceConnection : ServiceConnection {
         // A command that should be run when the service gets connected.
-        var pendingCommand: SenderCallbackCommand? = null
+        var pendingCommand: SenderServiceCommand? = null
 
         override fun onServiceConnected(className: ComponentName, service: IBinder) {
-            val newCallback = IDeviceSenderCallback.Stub.asInterface(service)
-            senderCallback = newCallback
+            val newCallback = IDeviceSenderService.Stub.asInterface(service)
+            senderService = newCallback
             pendingCommand?.run(newCallback)
             pendingCommand = null
         }
 
         override fun onServiceDisconnected(className: ComponentName) {
-            senderCallback = null
+            senderService = null
         }
     }
 
-    /** An interface defining a command that should be run on the sender callback. */
-    private fun interface SenderCallbackCommand {
-        /** Runs the command on the provided [senderCallback]. */
-        fun run(senderCallback: IDeviceSenderCallback)
-    }
-
-    private val fakeUndoRunnable = Runnable {
-        Log.i(TAG, "Undo runnable triggered")
+    /** An interface defining a command that should be run on the sender service. */
+    private fun interface SenderServiceCommand {
+        /** Runs the command on the provided [senderService]. */
+        fun run(senderService: IDeviceSenderService)
     }
 }
 
 @VisibleForTesting
-const val ADD_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-add-sender"
+const val SENDER_COMMAND = "media-ttt-chip-sender"
 @VisibleForTesting
 const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender"
 @VisibleForTesting
@@ -226,10 +243,21 @@
 @VisibleForTesting
 val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
 @VisibleForTesting
-val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
+val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!!
 @VisibleForTesting
-val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!
+val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME =
+    TransferToThisDeviceTriggered::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME =
+    TransferToThisDeviceSucceeded::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!!
+@VisibleForTesting
+val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver"
 
-private const val FUTURE_WAIT_TIME = 2000L
 private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon"
 private const val TAG = "MediaTapToTransferCli"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 67721a5..adae07b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -81,6 +81,9 @@
 
     /** Hides the chip. */
     fun removeChip() {
+        // TODO(b/203800347): We may not want to hide the chip if we're currently in a
+        //  TransferTriggered state: Once the user has initiated the transfer, they should be able
+        //  to move away from the receiver device but still see the status of the transfer.
         if (chipView == null) { return }
         windowManager.removeView(chipView)
         chipView = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index dd434e7..c656df2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -16,11 +16,12 @@
 
 package com.android.systemui.media.taptotransfer.sender
 
+import android.content.Context
 import android.graphics.drawable.Drawable
-import androidx.annotation.StringRes
+import android.view.View
 import com.android.systemui.R
 import com.android.systemui.media.taptotransfer.common.MediaTttChipState
-import java.util.concurrent.Future
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
 
 /**
  * A class that stores all the information necessary to display the media tap-to-transfer chip on
@@ -28,66 +29,181 @@
  *
  * This is a sealed class where each subclass represents a specific chip state. Each subclass can
  * contain additional information that is necessary for only that state.
- *
- * @property chipText a string resource for the text that the chip should display.
- * @property otherDeviceName the name of the other device involved in the transfer.
  */
 sealed class ChipStateSender(
     appIconDrawable: Drawable,
-    appIconContentDescription: String,
-    @StringRes internal val chipText: Int,
-    internal val otherDeviceName: String,
-) : MediaTttChipState(appIconDrawable, appIconContentDescription)
+    appIconContentDescription: String
+) : MediaTttChipState(appIconDrawable, appIconContentDescription) {
+    /** Returns a fully-formed string with the text that the chip should display. */
+    abstract fun getChipTextString(context: Context): String
+
+    /** Returns true if the loading icon should be displayed and false otherwise. */
+    open fun showLoading(): Boolean = false
+
+    /**
+     * Returns a click listener for the undo button on the chip. Returns null if this chip state
+     * doesn't have an undo button.
+     *
+     * @param controllerSender passed as a parameter in case we want to display a new chip state
+     *   when undo is clicked.
+     */
+    open fun undoClickListener(
+        controllerSender: MediaTttChipControllerSender
+    ): View.OnClickListener? = null
+}
 
 /**
  * A state representing that the two devices are close but not close enough to *start* a cast to
  * the receiver device. The chip will instruct the user to move closer in order to initiate the
  * transfer to the receiver.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
  */
 class MoveCloserToStartCast(
     appIconDrawable: Drawable,
     appIconContentDescription: String,
-    otherDeviceName: String,
-) : ChipStateSender(
-    appIconDrawable,
-    appIconContentDescription,
-    R.string.media_move_closer_to_start_cast,
-    otherDeviceName
-)
+    private val otherDeviceName: String,
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName)
+    }
+}
 
 /**
- * A state representing that a transfer has been initiated (but not completed).
+ * A state representing that the two devices are close but not close enough to *end* a cast that's
+ * currently occurring the receiver device. The chip will instruct the user to move closer in order
+ * to initiate the transfer from the receiver and back onto this device (the original sender).
  *
- * @property future a future that will be resolved when the transfer has either succeeded or failed.
- *   If the transfer succeeded, the future can optionally return an undo runnable (see
- *   [TransferSucceeded.undoRunnable]). [MediaTttChipControllerSender] is responsible for transitioning
- *   the chip to the [TransferSucceeded] state if the future resolves successfully.
+ * @property otherDeviceName the name of the other device involved in the transfer.
  */
-class TransferInitiated(
+class MoveCloserToEndCast(
     appIconDrawable: Drawable,
     appIconContentDescription: String,
-    otherDeviceName: String,
-    val future: Future<Runnable?>
-) : ChipStateSender(
-    appIconDrawable,
-    appIconContentDescription,
-    R.string.media_transfer_playing,
-    otherDeviceName
-)
+    private val otherDeviceName: String,
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName)
+    }
+}
 
 /**
- * A state representing that a transfer has been successfully completed.
+ * A state representing that a transfer to the receiver device has been initiated (but not
+ * completed).
  *
- * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will
- *   show an Undo button on the chip if this runnable is present.
+ * @property otherDeviceName the name of the other device involved in the transfer.
  */
-class TransferSucceeded(
+class TransferToReceiverTriggered(
     appIconDrawable: Drawable,
     appIconContentDescription: String,
-    otherDeviceName: String,
-    val undoRunnable: Runnable? = null
-) : ChipStateSender(appIconDrawable,
-    appIconContentDescription,
-    R.string.media_transfer_playing,
-    otherDeviceName
-)
+    private val otherDeviceName: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
+    }
+
+    override fun showLoading() = true
+}
+
+/**
+ * A state representing that a transfer from the receiver device and back to this device (the
+ * sender) has been initiated (but not completed).
+ */
+class TransferToThisDeviceTriggered(
+    appIconDrawable: Drawable,
+    appIconContentDescription: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_transfer_playing_this_device)
+    }
+
+    override fun showLoading() = true
+}
+
+/**
+ * A state representing that a transfer to the receiver device has been successfully completed.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
+ * @property undoCallback if present, the callback that should be called when the user clicks the
+ *   undo button. The undo button will only be shown if this is non-null.
+ */
+class TransferToReceiverSucceeded(
+    appIconDrawable: Drawable,
+    appIconContentDescription: String,
+    private val otherDeviceName: String,
+    val undoCallback: IUndoTransferCallback? = null
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
+    }
+
+    override fun undoClickListener(
+        controllerSender: MediaTttChipControllerSender
+    ): View.OnClickListener? {
+        if (undoCallback == null) {
+            return null
+        }
+
+        return View.OnClickListener {
+            this.undoCallback.onUndoTriggered()
+            // The external service should eventually send us a TransferToThisDeviceTriggered state,
+            // but that may take too long to go through the binder and the user may be confused as
+            // to why the UI hasn't changed yet. So, we immediately change the UI here.
+            controllerSender.displayChip(
+                TransferToThisDeviceTriggered(
+                    this.appIconDrawable,
+                    this.appIconContentDescription
+                )
+            )
+        }
+    }
+}
+
+/**
+ * A state representing that a transfer back to this device has been successfully completed.
+ *
+ * @property otherDeviceName the name of the other device involved in the transfer.
+ * @property undoCallback if present, the callback that should be called when the user clicks the
+ *   undo button. The undo button will only be shown if this is non-null.
+ */
+class TransferToThisDeviceSucceeded(
+    appIconDrawable: Drawable,
+    appIconContentDescription: String,
+    private val otherDeviceName: String,
+    val undoCallback: IUndoTransferCallback? = null
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_transfer_playing_this_device)
+    }
+
+    override fun undoClickListener(
+        controllerSender: MediaTttChipControllerSender
+    ): View.OnClickListener? {
+        if (undoCallback == null) {
+            return null
+        }
+
+        return View.OnClickListener {
+            this.undoCallback.onUndoTriggered()
+            // The external service should eventually send us a TransferToReceiverTriggered state,
+            // but that may take too long to go through the binder and the user may be confused as
+            // to why the UI hasn't changed yet. So, we immediately change the UI here.
+            controllerSender.displayChip(
+                TransferToReceiverTriggered(
+                    this.appIconDrawable,
+                    this.appIconContentDescription,
+                    this.otherDeviceName
+                )
+            )
+        }
+    }
+}
+
+/** A state representing that a transfer has failed. */
+class TransferFailed(
+    appIconDrawable: Drawable,
+    appIconContentDescription: String
+) : ChipStateSender(appIconDrawable, appIconContentDescription) {
+    override fun getChipTextString(context: Context): String {
+        return context.getString(R.string.media_transfer_failed)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 77d3d70..453e3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -23,11 +23,7 @@
 import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
-import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 /**
@@ -38,8 +34,6 @@
 class MediaTttChipControllerSender @Inject constructor(
     context: Context,
     windowManager: WindowManager,
-    @Main private val mainExecutor: Executor,
-    @Background private val backgroundExecutor: Executor,
 ) : MediaTttChipControllerCommon<ChipStateSender>(
     context, windowManager, R.layout.media_ttt_chip
 ) {
@@ -51,63 +45,22 @@
 
         // Text
         currentChipView.requireViewById<TextView>(R.id.text).apply {
-            text = context.getString(chipState.chipText, chipState.otherDeviceName)
+            text = chipState.getChipTextString(context)
         }
 
         // Loading
-        val showLoading = chipState is TransferInitiated
         currentChipView.requireViewById<View>(R.id.loading).visibility =
-            if (showLoading) { View.VISIBLE } else { View.GONE }
+            if (chipState.showLoading()) { View.VISIBLE } else { View.GONE }
 
         // Undo
-        val undoClickListener: View.OnClickListener? =
-            if (chipState is TransferSucceeded && chipState.undoRunnable != null)
-                View.OnClickListener { chipState.undoRunnable.run() }
-            else
-                null
         val undoView = currentChipView.requireViewById<View>(R.id.undo)
-        undoView.visibility = if (undoClickListener != null) {
-            View.VISIBLE
-        } else {
-            View.GONE
-        }
+        val undoClickListener = chipState.undoClickListener(this)
         undoView.setOnClickListener(undoClickListener)
+        undoView.visibility = if (undoClickListener != null) { View.VISIBLE } else { View.GONE }
 
-        // Future handling
-        if (chipState is TransferInitiated) {
-            addFutureCallback(chipState)
-        }
-    }
-
-    /**
-     * Adds the appropriate callbacks to [chipState.future] so that we update the chip correctly
-     * when the future resolves.
-     */
-    private fun addFutureCallback(chipState: TransferInitiated) {
-        // Listen to the future on a background thread so we don't occupy the main thread while we
-        // wait for it to complete.
-        backgroundExecutor.execute {
-            try {
-                val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS)
-                // Make UI changes on the main thread
-                mainExecutor.execute {
-                    displayChip(
-                        TransferSucceeded(
-                            chipState.appIconDrawable,
-                            chipState.appIconContentDescription,
-                            chipState.otherDeviceName,
-                            undoRunnable
-                        )
-                    )
-                }
-            } catch (ex: Exception) {
-                // TODO(b/203800327): Maybe show a failure chip here if UX decides we need one.
-                mainExecutor.execute {
-                    removeChip()
-                }
-            }
-        }
+        // Failure
+        val showFailure = chipState is TransferFailed
+        currentChipView.requireViewById<View>(R.id.failure_icon).visibility =
+            if (showFailure) { View.VISIBLE } else { View.GONE }
     }
 }
-
-private const val TRANSFER_TIMEOUT_SECONDS = 10L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
index b56a699..717752e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt
@@ -25,7 +25,8 @@
 import android.os.IBinder
 import com.android.systemui.R
 import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
 import javax.inject.Inject
 
 /**
@@ -37,12 +38,63 @@
 ) : Service() {
 
     // TODO(b/203800643): Add logging when callbacks trigger.
-    private val binder: IBinder = object : IDeviceSenderCallback.Stub() {
+    private val binder: IBinder = object : IDeviceSenderService.Stub() {
         override fun closeToReceiverToStartCast(
             mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
         ) {
             this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
         }
+
+        override fun closeToReceiverToEndCast(
+            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+        }
+
+        override fun transferFailed(
+            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.transferFailed(mediaInfo)
+        }
+
+        override fun transferToReceiverTriggered(
+            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo)
+        }
+
+        override fun transferToThisDeviceTriggered(
+            mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo)
+        }
+
+        override fun transferToReceiverSucceeded(
+            mediaInfo: MediaRoute2Info,
+            otherDeviceInfo: DeviceInfo,
+            undoCallback: IUndoTransferCallback
+        ) {
+            this@MediaTttSenderService.transferToReceiverSucceeded(
+                mediaInfo, otherDeviceInfo, undoCallback
+            )
+        }
+
+        override fun transferToThisDeviceSucceeded(
+            mediaInfo: MediaRoute2Info,
+            otherDeviceInfo: DeviceInfo,
+            undoCallback: IUndoTransferCallback
+        ) {
+            this@MediaTttSenderService.transferToThisDeviceSucceeded(
+                mediaInfo, otherDeviceInfo, undoCallback
+            )
+        }
+
+        override fun noLongerCloseToReceiver(
+            mediaInfo: MediaRoute2Info,
+            otherDeviceInfo: DeviceInfo
+        ) {
+            this@MediaTttSenderService.noLongerCloseToReceiver()
+        }
     }
 
     // TODO(b/203800643): Use the app icon from the media info instead of a fake one.
@@ -63,4 +115,68 @@
         )
         controller.displayChip(chipState)
     }
+
+    private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) {
+        val chipState = MoveCloserToEndCast(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString(),
+            otherDeviceName = otherDeviceInfo.name
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun transferFailed(mediaInfo: MediaRoute2Info) {
+        val chipState = TransferFailed(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString()
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun transferToReceiverTriggered(
+        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+    ) {
+        val chipState = TransferToReceiverTriggered(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString(),
+            otherDeviceName = otherDeviceInfo.name
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) {
+        val chipState = TransferToThisDeviceTriggered(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString()
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun transferToReceiverSucceeded(
+        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
+    ) {
+        val chipState = TransferToReceiverSucceeded(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString(),
+            otherDeviceName = otherDeviceInfo.name,
+            undoCallback = undoCallback
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun transferToThisDeviceSucceeded(
+        mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback
+    ) {
+        val chipState = TransferToThisDeviceSucceeded(
+            appIconDrawable = fakeAppIconDrawable,
+            appIconContentDescription = mediaInfo.name.toString(),
+            otherDeviceName = otherDeviceInfo.name,
+            undoCallback = undoCallback
+        )
+        controller.displayChip(chipState)
+    }
+
+    private fun noLongerCloseToReceiver() {
+        controller.removeChip()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 60060aa..00a3149 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -31,9 +31,9 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_STARTING_WINDOW;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
@@ -83,6 +83,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBar;
@@ -97,7 +98,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -161,7 +161,7 @@
     private final CommandQueue mCommandQueue;
     private final ShellTransitions mShellTransitions;
     private final Optional<StartingSurface> mStartingSurface;
-    private final SmartspaceTransitionController mSmartspaceTransitionController;
+    private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
     private final Optional<RecentTasks> mRecentTasks;
     private final UiEventLogger mUiEventLogger;
 
@@ -503,8 +503,8 @@
                     KEY_EXTRA_SHELL_STARTING_WINDOW,
                     startingwindow.createExternalInterface().asBinder()));
             params.putBinder(
-                    KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,
-                    mSmartspaceTransitionController.createExternalInterface().asBinder());
+                    KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
+                    mSysuiUnlockAnimationController.asBinder());
             mRecentTasks.ifPresent(recentTasks -> params.putBinder(
                     KEY_EXTRA_RECENT_TASKS,
                     recentTasks.createExternalInterface().asBinder()));
@@ -570,8 +570,8 @@
             BroadcastDispatcher broadcastDispatcher,
             ShellTransitions shellTransitions,
             ScreenLifecycle screenLifecycle,
-            SmartspaceTransitionController smartspaceTransitionController,
             UiEventLogger uiEventLogger,
+            KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             DumpManager dumpManager) {
         super(broadcastDispatcher);
         mContext = context;
@@ -644,7 +644,7 @@
         updateEnabledState();
         startConnectionToCurrentUser();
         mStartingSurface = startingSurface;
-        mSmartspaceTransitionController = smartspaceTransitionController;
+        mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
deleted file mode 100644
index 89b3df0..0000000
--- a/packages/SystemUI/src/com/android/systemui/shared/system/smartspace/SmartspaceTransitionController.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2021 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.shared.system.smartspace
-
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.shared.system.ActivityManagerWrapper
-import com.android.systemui.shared.system.QuickStepContract
-import kotlin.math.min
-
-/**
- * Controller that keeps track of SmartSpace instances in remote processes (such as Launcher),
- * allowing System UI to query or update their state during shared-element transitions.
- */
-class SmartspaceTransitionController {
-
-    /**
-     * Implementation of [ISmartspaceTransitionController] that we provide to Launcher, allowing it
-     * to provide us with a callback to query and update the state of its Smartspace.
-     */
-    private val ISmartspaceTransitionController = object : ISmartspaceTransitionController.Stub() {
-        override fun setSmartspace(callback: ISmartspaceCallback?) {
-            this@SmartspaceTransitionController.launcherSmartspace = callback
-            updateLauncherSmartSpaceState()
-        }
-    }
-
-    /**
-     * Callback provided by Launcher to allow us to query and update the state of its SmartSpace.
-     */
-    public var launcherSmartspace: ISmartspaceCallback? = null
-
-    public var lockscreenSmartspace: View? = null
-
-    /**
-     * Cached state of the Launcher SmartSpace. Retrieving the state is an IPC, so we should avoid
-     * unnecessary
-     */
-    public var mLauncherSmartspaceState: SmartspaceState? = null
-
-    /**
-     * The bounds of our SmartSpace when the shared element transition began. We'll interpolate
-     * between this and [smartspaceDestinationBounds] as the dismiss amount changes.
-     */
-    private val smartspaceOriginBounds = Rect()
-
-    /** The bounds of the Launcher's SmartSpace, which is where we are animating our SmartSpace. */
-
-    private val smartspaceDestinationBounds = Rect()
-
-    fun createExternalInterface(): ISmartspaceTransitionController {
-        return ISmartspaceTransitionController
-    }
-
-    /**
-     * Updates [mLauncherSmartspaceState] and returns it. This will trigger a binder call, so use the
-     * cached [mLauncherSmartspaceState] if possible.
-     */
-    fun updateLauncherSmartSpaceState(): SmartspaceState? {
-        return launcherSmartspace?.smartspaceState.also {
-            mLauncherSmartspaceState = it
-        }
-    }
-
-    fun prepareForUnlockTransition() {
-        updateLauncherSmartSpaceState().also { state ->
-            if (state?.boundsOnScreen != null && lockscreenSmartspace != null) {
-                lockscreenSmartspace!!.getBoundsOnScreen(smartspaceOriginBounds)
-                with(smartspaceDestinationBounds) {
-                    set(state.boundsOnScreen)
-                    offset(-lockscreenSmartspace!!.paddingLeft,
-                            -lockscreenSmartspace!!.paddingTop)
-                }
-            }
-        }
-    }
-
-    fun setProgressToDestinationBounds(progress: Float) {
-        if (!isSmartspaceTransitionPossible()) {
-            return
-        }
-
-        val progressClamped = min(1f, progress)
-
-        // Calculate the distance (relative to the origin) that we need to be for the current
-        // progress value.
-        val progressX =
-                (smartspaceDestinationBounds.left - smartspaceOriginBounds.left) * progressClamped
-        val progressY =
-                (smartspaceDestinationBounds.top - smartspaceOriginBounds.top) * progressClamped
-
-        val lockscreenSmartspaceCurrentBounds = Rect().also {
-            lockscreenSmartspace!!.getBoundsOnScreen(it)
-        }
-
-        // Figure out how far that is from our present location on the screen. This approach
-        // compensates for the fact that our parent container is also translating to animate out.
-        val dx = smartspaceOriginBounds.left + progressX -
-                lockscreenSmartspaceCurrentBounds.left
-        var dy = smartspaceOriginBounds.top + progressY -
-                lockscreenSmartspaceCurrentBounds.top
-
-        with(lockscreenSmartspace!!) {
-            translationX = translationX + dx
-            translationY = translationY + dy
-        }
-    }
-
-    /**
-     * Whether we're capable of performing the Smartspace shared element transition when we unlock.
-     * This is true if:
-     *
-     * - The Launcher registered a Smartspace with us, it's reporting non-empty bounds on screen.
-     * - Launcher is behind the keyguard, and the Smartspace is visible on the currently selected
-     *   page.
-     */
-    public fun isSmartspaceTransitionPossible(): Boolean {
-        val smartSpaceNullOrBoundsEmpty = mLauncherSmartspaceState?.boundsOnScreen?.isEmpty ?: true
-        return isLauncherUnderneath() && !smartSpaceNullOrBoundsEmpty
-    }
-
-    companion object {
-        fun isLauncherUnderneath(): Boolean {
-            return ActivityManagerWrapper.getInstance()
-                    .runningTask?.topActivity?.className?.equals(
-                            QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 2a21f42..6cfbb43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -320,7 +320,7 @@
          * @param updatePostTime whether or not to refresh the post time
          */
         public void updateEntry(boolean updatePostTime) {
-            mLogger.logUpdateEntry(updatePostTime);
+            mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime);
 
             long currentTime = mClock.currentTimeMillis();
             mEarliestRemovaltime = currentTime + mMinimumDisplayTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 46004db..267ee6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -154,6 +154,16 @@
         }
 
     /**
+     * We're unlocking, and should not blur as the panel expansion changes.
+     */
+    var blursDisabledForUnlock: Boolean = false
+    set(value) {
+        if (field == value) return
+        field = value
+        scheduleUpdate()
+    }
+
+    /**
      * Force stop blur effect when necessary.
      */
     private var scrimsVisible: Boolean = false
@@ -192,7 +202,7 @@
         combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
         var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
 
-        if (blursDisabledForAppLaunch) {
+        if (blursDisabledForAppLaunch || blursDisabledForUnlock) {
             shadeRadius = 0f
         }
 
@@ -309,9 +319,7 @@
     /**
      * Update blurs when pulling down the shade
      */
-    override fun onPanelExpansionChanged(
-        rawFraction: Float, expanded: Boolean, tracking: Boolean
-    ) {
+    override fun onPanelExpansionChanged(rawFraction: Float, expanded: Boolean, tracking: Boolean) {
         val timestamp = SystemClock.elapsedRealtimeNanos()
         val expansion = MathUtils.saturate(
                 (rawFraction - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index f22acb7..0fd9272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -638,22 +638,6 @@
         if (row != null) row.setHeadsUpAnimatingAway(animatingAway);
     }
 
-    /**
-     * Set that this notification was automatically heads upped. This happens for example when
-     * the user bypasses the lockscreen and media is playing.
-     */
-    public void setAutoHeadsUp(boolean autoHeadsUp) {
-        mAutoHeadsUp = autoHeadsUp;
-    }
-
-    /**
-     * @return if this notification was automatically heads upped. This happens for example when
-     *      * the user bypasses the lockscreen and media is playing.
-     */
-    public boolean isAutoHeadsUp() {
-        return mAutoHeadsUp;
-    }
-
     public boolean mustStayOnScreen() {
         return row != null && row.mustStayOnScreen();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index b84b382..0bf21af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -100,7 +100,7 @@
             if (wasHeadsUp) {
                 if (shouldHeadsUp) {
                     mHeadsUpManager.updateNotification(entry.key, hunAgain)
-                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.key)) {
+                } else {
                     // We don't want this to be interrupting anymore, let's remove it
                     mHeadsUpManager.removeNotification(
                         entry.key, false /* removeImmediately */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 289dacb..26ba12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -41,17 +41,29 @@
 
     fun getChildCount(): Int = 0
 
+    /** Called to add a child to this view */
     fun addChildAt(child: NodeController, index: Int) {
         throw RuntimeException("Not supported")
     }
 
+    /** Called to move one of this view's current children to a new position */
     fun moveChildTo(child: NodeController, index: Int) {
         throw RuntimeException("Not supported")
     }
 
+    /** Called to remove one of this view's current children */
     fun removeChild(child: NodeController, isTransfer: Boolean) {
         throw RuntimeException("Not supported")
     }
+
+    /** Called when this view has been added */
+    fun onViewAdded() {}
+
+    /** Called when this view has been moved */
+    fun onViewMoved() {}
+
+    /** Called when this view has been removed */
+    fun onViewRemoved() {}
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 4e9017e..2c9508e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -94,6 +94,10 @@
         _view?.setOnClearAllClickListener(listener)
     }
 
+    override fun onViewAdded() {
+        headerView?.isContentVisible = true
+    }
+
     override val view: View
         get() = _view!!
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 6d4ae4b..28cd285 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -215,13 +215,16 @@
 
     fun addChildAt(child: ShadeNode, index: Int) {
         controller.addChildAt(child.controller, index)
+        child.controller.onViewAdded()
     }
 
     fun moveChildTo(child: ShadeNode, index: Int) {
         controller.moveChildTo(child.controller, index)
+        child.controller.onViewMoved()
     }
 
     fun removeChild(child: ShadeNode, isTransfer: Boolean) {
         controller.removeChild(child.controller, isTransfer)
+        child.controller.onViewRemoved()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
deleted file mode 100644
index b61a540..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2019 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.statusbar.notification.interruption
-
-import android.content.Context
-import android.media.MediaMetadata
-import android.provider.Settings
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.NotificationMediaManager
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.tuner.TunerService
-import javax.inject.Inject
-
-/**
- * A class that automatically creates heads up for important notification when bypassing the
- * lockscreen
- */
-@SysUISingleton
-class BypassHeadsUpNotifier @Inject constructor(
-    private val context: Context,
-    private val bypassController: KeyguardBypassController,
-    private val statusBarStateController: StatusBarStateController,
-    private val headsUpManager: HeadsUpManagerPhone,
-    private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
-    private val mediaManager: NotificationMediaManager,
-    private val commonNotifCollection: CommonNotifCollection,
-    tunerService: TunerService
-) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
-
-    private var currentMediaEntry: NotificationEntry? = null
-    private var enabled = true
-
-    var fullyAwake = false
-        set(value) {
-            field = value
-            if (value) {
-                updateAutoHeadsUp(currentMediaEntry)
-            }
-        }
-
-    init {
-        statusBarStateController.addCallback(this)
-        tunerService.addTunable(
-                TunerService.Tunable { _, _ ->
-                    enabled = Settings.Secure.getIntForUser(
-                            context.contentResolver,
-                            Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING,
-                            0 /* default */,
-                            KeyguardUpdateMonitor.getCurrentUser()) != 0
-                }, Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING)
-    }
-
-    fun setUp() {
-        mediaManager.addCallback(this)
-    }
-
-    override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
-        val previous = currentMediaEntry
-        val mediaNotificationKey = mediaManager.mediaNotificationKey
-        currentMediaEntry =
-            if (mediaNotificationKey != null && NotificationMediaManager.isPlayingState(state))
-                commonNotifCollection.getEntry(mediaNotificationKey)
-            else null
-        updateAutoHeadsUp(previous)
-        updateAutoHeadsUp(currentMediaEntry)
-    }
-
-    private fun updateAutoHeadsUp(entry: NotificationEntry?) {
-        entry?.let {
-            val autoHeadsUp = it == currentMediaEntry && canAutoHeadsUp(it)
-            it.isAutoHeadsUp = autoHeadsUp
-            if (autoHeadsUp) {
-                headsUpManager.showNotification(it)
-            }
-        }
-    }
-
-    /**
-     * @return {@code true} if this entry be autoHeadsUpped right now.
-     */
-    private fun canAutoHeadsUp(entry: NotificationEntry): Boolean {
-        if (!isAutoHeadsUpAllowed()) {
-            return false
-        }
-        if (entry.isSensitive) {
-            // filter sensitive notifications
-            return false
-        }
-        if (!notificationLockscreenUserManager.shouldShowOnKeyguard(entry)) {
-            // filter notifications invisible on Keyguard
-            return false
-        }
-        if (commonNotifCollection.getEntry(entry.key) != null) {
-            // filter notifications not the active list currently
-            return false
-        }
-        return true
-    }
-
-    override fun onStatePostChange() {
-        updateAutoHeadsUp(currentMediaEntry)
-    }
-
-    /**
-     * @return {@code true} if autoHeadsUp is possible right now.
-     */
-    private fun isAutoHeadsUpAllowed(): Boolean {
-        if (!enabled) {
-            return false
-        }
-        if (!bypassController.bypassEnabled) {
-            return false
-        }
-        if (statusBarStateController.state != StatusBarState.KEYGUARD) {
-            return false
-        }
-        if (!fullyAwake) {
-            return false
-        }
-        return true
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index b1c69e4..74fb3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -124,7 +124,7 @@
         if (wasHeadsUp) {
             if (shouldHeadsUp) {
                 mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
-            } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
+            } else {
                 // We don't want this to be interrupting anymore, let's remove it
                 mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 63cb4ae..5d6d0f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -424,7 +424,8 @@
     }
 
     @Override
-    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onFinishRunnable) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAppear;
         if (mDrawingAppearAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index b28fb58..46efef6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -268,6 +268,18 @@
     }
 
     @Override
+    public void onViewAdded() {
+    }
+
+    @Override
+    public void onViewMoved() {
+    }
+
+    @Override
+    public void onViewRemoved() {
+    }
+
+    @Override
     public int getChildCount() {
         final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
         return mChildren != null ? mChildren.size() : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6eff799..8ffdb69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -358,7 +358,12 @@
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
-    public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+        performAddAnimation(delay, duration, isHeadsUpAppear, null);
+    }
+
+    public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onEndRunnable);
 
     /**
      * Set the notification appearance to be below the speed bump.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index f26598d..ec406f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.LogLevel.WARNING
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
@@ -50,7 +49,7 @@
     }
 
     fun logRequestPipelineRowNotSet(notifKey: String) {
-        buffer.log(TAG, WARNING, {
+        buffer.log(TAG, INFO, {
             str1 = notifKey
         }, {
             "Row is not set so pipeline will not run. notif = $str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 9c755e9..aa3e027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -105,6 +105,9 @@
                 runAfter.run();
             };
             setViewVisible(mContent, visible, animate, endRunnable);
+        } else if (runAfter != null) {
+            // Execute the runAfter runnable immediately if there's no animation to perform.
+            runAfter.run();
         }
 
         if (!mContentAnimating) {
@@ -228,7 +231,7 @@
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
-        setContentVisible(false);
+        setContentVisible(false, true /* animate */, onFinishedRunnable);
         return 0;
     }
 
@@ -239,6 +242,13 @@
     }
 
     @Override
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable endRunnable) {
+        // TODO: use delay and duration
+        setContentVisible(true);
+    }
+
+    @Override
     public boolean needsClippingToShelf() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
index c9a0f6c..bd5b7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java
@@ -39,7 +39,8 @@
     }
 
     @Override
-    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
+            Runnable onEnd) {
         // No animation, it doesn't need it, this would be local
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 90f5179..4283343 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5658,6 +5658,10 @@
         }
     }
 
+    protected void setLogger(StackStateLogger logger) {
+        mStateAnimator.setLogger(logger);
+    }
+
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5833ec2..51ce779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -184,6 +184,7 @@
     private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
+    private final StackStateLogger mStackStateLogger;
 
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
@@ -660,7 +661,9 @@
             NotificationRemoteInputManager remoteInputManager,
             VisualStabilityManager visualStabilityManager,
             ShadeController shadeController,
-            InteractionJankMonitor jankMonitor) {
+            InteractionJankMonitor jankMonitor,
+            StackStateLogger stackLogger) {
+        mStackStateLogger = stackLogger;
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
         mVisibilityProvider = visibilityProvider;
@@ -712,6 +715,7 @@
 
     public void attach(NotificationStackScrollLayout view) {
         mView = view;
+        mView.setLogger(mStackStateLogger);
         mView.setController(this);
         mView.setTouchHandler(new TouchHandler());
         mView.setStatusBar(mStatusBar);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 1d0d374..0d2bddc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -86,6 +86,7 @@
     private NotificationShelf mShelf;
     private float mStatusBarIconLocation;
     private int[] mTmpLocation = new int[2];
+    private StackStateLogger mLogger;
 
     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
@@ -113,6 +114,10 @@
         };
     }
 
+    protected void setLogger(StackStateLogger logger) {
+        mLogger = logger;
+    }
+
     public boolean isRunning() {
         return !mAnimatorSet.isEmpty();
     }
@@ -337,6 +342,12 @@
             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
+            boolean loggable = false;
+            String key = null;
+            if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
+                loggable = true;
+                key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
+            }
             if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
 
@@ -407,10 +418,22 @@
                 if (event.headsUpFromBottom) {
                     mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
                 } else {
+                    Runnable onAnimationEnd = null;
+                    if (loggable) {
+                        String finalKey = key;
+                        onAnimationEnd = () -> mLogger.appearAnimationEnded(finalKey);
+                    }
                     changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR,
-                            true /* isHeadsUpAppear */);
+                            true /* isHeadsUpAppear */, onAnimationEnd);
                 }
                 mHeadsUpAppearChildren.add(changingView);
+                // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal
+                // ADD animations, which would not be logged here.
+                if (loggable) {
+                    mLogger.logHUNViewAppearing(
+                            ((ExpandableNotificationRow) changingView).getEntry().getKey());
+                }
+
                 mTmpState.applyToView(changingView);
             } else if (event.animationType == NotificationStackScrollLayout
                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
@@ -453,10 +476,21 @@
                     // We need to add the global animation listener, since once no animations are
                     // running anymore, the panel will instantly hide itself. We need to wait until
                     // the animation is fully finished for this though.
+                    Runnable postAnimation = endRunnable;
+                    if (loggable) {
+                        mLogger.logHUNViewDisappearing(key);
+
+                        Runnable finalEndRunnable = endRunnable;
+                        String finalKey1 = key;
+                        postAnimation = () -> {
+                            mLogger.disappearAnimationEnded(finalKey1);
+                            if (finalEndRunnable != null) finalEndRunnable.run();
+                        };
+                    }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
-                            endRunnable, getGlobalAnimationFinishedListener());
+                            postAnimation, getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
                     endRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
new file mode 100644
index 0000000..4315265
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import javax.inject.Inject
+
+class StackStateLogger @Inject constructor(
+    @NotificationHeadsUpLog private val buffer: LogBuffer
+) {
+    fun logHUNViewDisappearing(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up view disappearing $str1 "
+        })
+    }
+
+    fun logHUNViewAppearing(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification view appearing $str1 "
+        })
+    }
+
+    fun disappearAnimationEnded(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification disappear animation ended $str1 "
+        })
+    }
+
+    fun appearAnimationEnded(key: String) {
+        buffer.log(TAG, LogLevel.INFO, {
+            str1 = key
+        }, {
+            "Heads up notification appear animation ended $str1 "
+        })
+    }
+}
+
+private const val TAG = "StackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index dee1b33..8d500fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -254,6 +255,8 @@
                 );
     }
 
+    private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -269,7 +272,8 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             ScreenLifecycle screenLifecycle,
             AuthController authController,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
         mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
@@ -292,6 +296,7 @@
         mMetricsLogger = metricsLogger;
         mAuthController = authController;
         mStatusBarStateController = statusBarStateController;
+        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         dumpManager.registerDumpable(getClass().getName(), this);
     }
 
@@ -438,11 +443,15 @@
                 if (!wasDeviceInteractive) {
                     mPendingShowBouncer = true;
                 } else {
-                    mShadeController.animateCollapsePanels(
-                            CommandQueue.FLAG_EXCLUDE_NONE,
-                            true /* force */,
-                            false /* delayed */,
-                            BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+                    // If the keyguard unlock controller is going to handle the unlock animation, it
+                    // will fling the panel collapsed when it's ready.
+                    if (!mKeyguardUnlockAnimationController.willHandleUnlockAnimation()) {
+                        mShadeController.animateCollapsePanels(
+                                CommandQueue.FLAG_EXCLUDE_NONE,
+                                true /* force */,
+                                false /* delayed */,
+                                BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+                    }
                     mPendingShowBouncer = false;
                     mKeyguardViewController.notifyKeyguardAuthenticated(
                             false /* strongAuth */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 2824ab8..77cff34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -60,16 +60,14 @@
     private final KeyguardBypassController mBypassController;
     private final GroupMembershipManager mGroupMembershipManager;
     private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
-    private final int mAutoHeadsUpNotificationDecay;
     // TODO (b/162832756): remove visual stability manager when migrating to new pipeline
     private VisualStabilityManager mVisualStabilityManager;
     private boolean mReleaseOnExpandFinish;
 
     private boolean mTrackingHeadsUp;
-    private HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    private HashSet<String> mKeysToRemoveWhenLeavingKeyguard = new HashSet<>();
-    private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    private final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
             = new ArraySet<>();
     private boolean mIsExpanded;
     private boolean mHeadsUpGoingAway;
@@ -110,8 +108,6 @@
         super(context, logger);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
-        mAutoHeadsUpNotificationDecay = resources.getInteger(
-                R.integer.auto_heads_up_notification_decay);
         statusBarStateController.addCallback(mStatusBarStateListener);
         mBypassController = bypassController;
         mGroupMembershipManager = groupMembershipManager;
@@ -234,15 +230,6 @@
         }
     }
 
-    @Override
-    public boolean isEntryAutoHeadsUpped(String key) {
-        HeadsUpEntryPhone headsUpEntryPhone = getHeadsUpEntryPhone(key);
-        if (headsUpEntryPhone == null) {
-            return false;
-        }
-        return headsUpEntryPhone.isAutoHeadsUp();
-    }
-
     /**
      * Set that we are exiting the headsUp pinned mode, but some notifications might still be
      * animating out. This is used to keep the touchable regions in a reasonable state.
@@ -375,7 +362,6 @@
 
     @Override
     protected void onAlertEntryRemoved(AlertEntry alertEntry) {
-        mKeysToRemoveWhenLeavingKeyguard.remove(alertEntry.mEntry.getKey());
         super.onAlertEntryRemoved(alertEntry);
         mEntryPool.release((HeadsUpEntryPhone) alertEntry);
     }
@@ -437,11 +423,6 @@
          */
         private boolean extended;
 
-        /**
-         * Was this entry received while on keyguard
-         */
-        private boolean mIsAutoHeadsUp;
-
 
         @Override
         public boolean isSticky() {
@@ -459,8 +440,6 @@
                             false  /* persistent */);
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
-                } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
-                    mKeysToRemoveWhenLeavingKeyguard.add(entry.getKey());
                 } else {
                     removeAlertEntry(entry.getKey());
                 }
@@ -471,7 +450,6 @@
 
         @Override
         public void updateEntry(boolean updatePostTime) {
-            mIsAutoHeadsUp = mEntry.isAutoHeadsUp();
             super.updateEntry(updatePostTime);
 
             if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
@@ -480,7 +458,6 @@
             if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
                 mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
             }
-            mKeysToRemoveWhenLeavingKeyguard.remove(mEntry.getKey());
         }
 
         @Override
@@ -515,7 +492,6 @@
             super.reset();
             mMenuShownPinned = false;
             extended = false;
-            mIsAutoHeadsUp = false;
         }
 
         private void extendPulse() {
@@ -526,33 +502,8 @@
         }
 
         @Override
-        public int compareTo(AlertEntry alertEntry) {
-            HeadsUpEntryPhone headsUpEntry = (HeadsUpEntryPhone) alertEntry;
-            boolean autoShown = isAutoHeadsUp();
-            boolean otherAutoShown = headsUpEntry.isAutoHeadsUp();
-            if (autoShown && !otherAutoShown) {
-                return 1;
-            } else if (!autoShown && otherAutoShown) {
-                return -1;
-            }
-            return super.compareTo(alertEntry);
-        }
-
-        @Override
         protected long calculateFinishTime() {
-            return mPostTime + getDecayDuration() + (extended ? mExtensionTime : 0);
-        }
-
-        private int getDecayDuration() {
-            if (isAutoHeadsUp()) {
-                return getRecommendedHeadsUpTimeoutMs(mAutoHeadsUpNotificationDecay);
-            } else {
-                return getRecommendedHeadsUpTimeoutMs(mAutoDismissNotificationDecay);
-            }
-        }
-
-        private boolean isAutoHeadsUp() {
-            return mIsAutoHeadsUp;
+            return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
         }
     }
 
@@ -577,13 +528,6 @@
             boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
             boolean isKeyguard = newState == StatusBarState.KEYGUARD;
             mStatusBarState = newState;
-            if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
-                String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
-                for (String key : keys) {
-                    removeAlertEntry(key);
-                }
-                mKeysToRemoveWhenLeavingKeyguard.clear();
-            }
             if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
                 ArrayList<String> keysToRemove = new ArrayList<>();
                 for (AlertEntry entry : mAlertEntries.values()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 016b953..769f689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -138,6 +138,7 @@
 import com.android.systemui.idle.IdleHostView;
 import com.android.systemui.idle.IdleHostViewController;
 import com.android.systemui.idle.dagger.IdleViewComponent;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -791,7 +792,8 @@
             Optional<SysUIUnfoldComponent> unfoldComponent,
             ControlsComponent controlsComponent,
             InteractionJankMonitor interactionJankMonitor,
-            QsFrameTranslateController qsFrameTranslateController) {
+            QsFrameTranslateController qsFrameTranslateController,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
         super(view,
                 featureFlags,
                 falsingManager,
@@ -806,7 +808,8 @@
                 lockscreenGestureLogger,
                 panelExpansionStateManager,
                 ambientState,
-                interactionJankMonitor);
+                interactionJankMonitor,
+                keyguardUnlockAnimationController);
         mView = view;
         mVibratorHelper = vibratorHelper;
         mKeyguardMediaController = keyguardMediaController;
@@ -925,8 +928,33 @@
         mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
         onFinishInflate();
-
         mUseCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS);
+        keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
+                new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
+                    @Override
+                    public void onUnlockAnimationFinished() {
+                        // Make sure the clock is in the correct position after the unlock animation
+                        // so that it's not in the wrong place when we show the keyguard again.
+                        positionClockAndNotifications(true /* forceClockUpdate */);
+                    }
+
+                    @Override
+                    public void onUnlockAnimationStarted(
+                            boolean playingCannedAnimation, boolean isWakeAndUnlock) {
+                        // Disable blurs while we're unlocking so that panel expansion does not
+                        // cause blurring. This will eventually be re-enabled by the panel view on
+                        // ACTION_UP, since the user's finger might still be down after a swipe to
+                        // unlock gesture, and we don't want that to cause blurring either.
+                        mDepthController.setBlursDisabledForUnlock(mTracking);
+
+                        if (playingCannedAnimation && !isWakeAndUnlock) {
+                            // Fling the panel away so it's not in the way or the surface behind the
+                            // keyguard, which will be appearing. If we're wake and unlocking, the
+                            // lock screen is hidden instantly so should not be flung away.
+                            fling(0f, false, 0.7f, false);
+                        }
+                    }
+                });
     }
 
     private void onFinishInflate() {
@@ -3324,6 +3352,10 @@
                 mAffordanceHelper.reset(true);
             }
         }
+
+        // If we unlocked from a swipe, the user's finger might still be down after the
+        // unlock animation ends. We need to wait until ACTION_UP to enable blurs again.
+        mDepthController.setBlursDisabledForUnlock(false);
     }
 
     private void updateMaxHeadsUpTranslation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 53bfd77..05ac2a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -55,6 +55,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -212,6 +213,8 @@
         return mAmbientState;
     }
 
+    private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+
     public PanelViewController(
             PanelView view,
             FeatureFlags featureFlags,
@@ -227,7 +230,15 @@
             LockscreenGestureLogger lockscreenGestureLogger,
             PanelExpansionStateManager panelExpansionStateManager,
             AmbientState ambientState,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
+        mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
+        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onKeyguardFadingAwayChanged() {
+                requestPanelHeightUpdate();
+            }
+        });
         mAmbientState = ambientState;
         mView = view;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -437,7 +448,8 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (!mStatusBar.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+                && !mKeyguardStateController.isKeyguardGoingAway()) {
             boolean expands = onEmptySpaceClick(mInitialTouchX);
             onTrackingStopped(expands);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5d83cc6..48048b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -809,6 +809,12 @@
                     : ScrimState.SHADE_LOCKED.getBehindTint();
             behindTint = ColorUtils.blendARGB(behindTint, stateTint, mQsExpansion);
         }
+
+        // If the keyguard is going away, we should not be opaque.
+        if (mKeyguardStateController.isKeyguardGoingAway()) {
+            behindAlpha = 0f;
+        }
+
         return new Pair<>(behindTint, behindAlpha);
     }
 
@@ -1333,9 +1339,6 @@
 
     public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) {
         mExpansionAffectsAlpha = expansionAffectsAlpha;
-        if (expansionAffectsAlpha) {
-            applyAndDispatchState();
-        }
     }
 
     public void setKeyguardOccluded(boolean keyguardOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 07ae33c..455ffdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -211,7 +211,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -482,7 +481,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
-    private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
     private final FalsingCollector mFalsingCollector;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -619,6 +617,8 @@
     protected boolean mDozing;
     private boolean mIsFullscreen;
 
+    boolean mCloseQsBeforeScreenOff;
+
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final NotificationRemoteInputManager mRemoteInputManager;
@@ -701,7 +701,6 @@
             KeyguardStateController keyguardStateController,
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
@@ -798,7 +797,6 @@
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -915,7 +913,6 @@
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        mBypassHeadsUpNotifier.setUp();
         if (mBubblesOptional.isPresent()) {
             mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
         }
@@ -1124,6 +1121,15 @@
         }
         if (leaveOpen) {
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
+            if (mIsKeyguard) {
+                // When device state changes on keyguard we don't want to keep the state of
+                // the shade and instead we open clean state of keyguard with shade closed.
+                // Normally some parts of QS state (like expanded/collapsed) are persisted and
+                // that causes incorrect UI rendering, especially when changing state with QS
+                // expanded. To prevent that we can close QS which resets QS and some parts of
+                // the shade to its default state. Read more in b/201537421
+                mCloseQsBeforeScreenOff = true;
+            }
         }
     }
 
@@ -2923,10 +2929,10 @@
     }
 
     boolean updateIsKeyguard() {
-        return updateIsKeyguard(false /* force */);
+        return updateIsKeyguard(false /* forceStateChange */);
     }
 
-    boolean updateIsKeyguard(boolean force) {
+    boolean updateIsKeyguard(boolean forceStateChange) {
         boolean wakeAndUnlocking = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
@@ -2959,7 +2965,7 @@
             //    as the animation could prepare 'fake AOD' interface (without actually
             //    transitioning to keyguard state) and this might reset the view states
             if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
-                return hideKeyguardImpl(force);
+                return hideKeyguardImpl(forceStateChange);
             }
         }
         return false;
@@ -2967,6 +2973,8 @@
 
     public void showKeyguardImpl() {
         mIsKeyguard = true;
+        // In case we're locking while a smartspace transition is in progress, reset it.
+        mKeyguardUnlockAnimationController.resetSmartspaceTransition();
         if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
             mNotificationPanelViewController.cancelAnimation();
             onLaunchTransitionFadingEnded();
@@ -3087,12 +3095,12 @@
     /**
      * @return true if we would like to stay in the shade, false if it should go away entirely
      */
-    public boolean hideKeyguardImpl(boolean force) {
+    public boolean hideKeyguardImpl(boolean forceStateChange) {
         mIsKeyguard = false;
         Trace.beginSection("StatusBar#hideKeyguard");
         boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
         int previousState = mStatusBarStateController.getState();
-        if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) {
+        if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) {
             //TODO: StatusBarStateController should probably know about hiding the keyguard and
             // notify listeners.
 
@@ -3145,6 +3153,7 @@
         // bar.
         mKeyguardStateController.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
+        updateScrimController();
     }
 
     /**
@@ -3316,7 +3325,10 @@
     }
 
     private void showBouncerOrLockScreenIfKeyguard() {
-        if (!mKeyguardViewMediator.isHiding()) {
+        // If the keyguard is animating away, we aren't really the keyguard anymore and should not
+        // show the bouncer/lockscreen.
+        if (!mKeyguardViewMediator.isHiding()
+                && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
             if (mState == StatusBarState.SHADE_LOCKED
                     && mKeyguardUpdateMonitor.isUdfpsEnrolled()) {
                 // shade is showing while locked on the keyguard, so go back to showing the
@@ -3564,7 +3576,6 @@
             maybeEscalateHeadsUp();
             dismissVolumeDialog();
             mWakeUpCoordinator.setFullyAwake(false);
-            mBypassHeadsUpNotifier.setFullyAwake(false);
             mKeyguardBypassController.onStartedGoingToSleep();
 
             // The unlocked screen off and fold to aod animations might use our LightRevealScrim -
@@ -3606,7 +3617,6 @@
         @Override
         public void onFinishedWakingUp() {
             mWakeUpCoordinator.setFullyAwake(true);
-            mBypassHeadsUpNotifier.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false);
             if (mLaunchCameraWhenFinishedWaking) {
                 mNotificationPanelViewController.launchCamera(
@@ -3654,6 +3664,10 @@
         public void onScreenTurnedOff() {
             mFalsingCollector.onScreenOff();
             mScrimController.onScreenTurnedOff();
+            if (mCloseQsBeforeScreenOff) {
+                mNotificationPanelViewController.closeQs();
+                mCloseQsBeforeScreenOff = false;
+            }
             updateIsKeyguard();
         }
     };
@@ -3746,17 +3760,14 @@
     public void updateScrimController() {
         Trace.beginSection("StatusBar#updateScrimController");
 
-        // We don't want to end up in KEYGUARD state when we're unlocking with
-        // fingerprint from doze. We should cross fade directly from black.
-        boolean unlocking = mBiometricUnlockController.isWakeAndUnlock()
-                || mKeyguardStateController.isKeyguardFadingAway();
+        boolean unlocking = mKeyguardStateController.isShowing() && (
+                mBiometricUnlockController.isWakeAndUnlock()
+                        || mKeyguardStateController.isKeyguardFadingAway()
+                        || mKeyguardStateController.isKeyguardGoingAway()
+                        || mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard()
+                        || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
 
-        // Do not animate the scrim expansion when triggered by the fingerprint sensor.
-        boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing()
-                || mKeyguardStateController.isKeyguardFadingAway()
-                || mKeyguardStateController.isKeyguardGoingAway();
-        mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock()
-                        && onKeyguardOrHidingIt));
+        mScrimController.setExpansionAffectsAlpha(!unlocking);
 
         boolean launchingAffordanceWithPreview =
                 mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
@@ -3768,7 +3779,7 @@
             } else {
                 mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
             }
-        } else if (mBouncerShowing) {
+        } else if (mBouncerShowing && !unlocking) {
             // Bouncer needs the front scrim when it's on top of an activity,
             // tapping on a notification, editing QS or being dismissed by
             // FLAG_DISMISS_KEYGUARD_ACTIVITY.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 6746b3e..cc65ca02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -6,6 +6,7 @@
 import android.content.Context
 import android.database.ContentObserver
 import android.os.Handler
+import android.os.PowerManager
 import android.provider.Settings
 import android.view.Surface
 import android.view.View
@@ -54,9 +55,10 @@
     private val keyguardStateController: KeyguardStateController,
     private val dozeParameters: dagger.Lazy<DozeParameters>,
     private val globalSettings: GlobalSettings,
-    private val interactionJankMonitor: InteractionJankMonitor
+    private val interactionJankMonitor: InteractionJankMonitor,
+    private val powerManager: PowerManager,
+    private val handler: Handler = Handler()
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
-    private val handler = Handler()
 
     private lateinit var statusBar: StatusBar
     private lateinit var lightRevealScrim: LightRevealScrim
@@ -219,7 +221,7 @@
             // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
             // changed parts of the UI (such as showing AOD in the shade) without actually changing
             // the StatusBarState. This ensures that the UI definitely reflects the desired state.
-            statusBar.updateIsKeyguard(true /* force */)
+            statusBar.updateIsKeyguard(true /* forceStateChange */)
         }
     }
 
@@ -231,10 +233,18 @@
             lightRevealAnimationPlaying = true
             lightRevealAnimator.start()
             handler.postDelayed({
-                aodUiAnimationPlaying = true
+                // Only run this callback if the device is sleeping (not interactive). This callback
+                // is removed in onStartedWakingUp, but since that event is asynchronously
+                // dispatched, a race condition could make it possible for this callback to be run
+                // as the device is waking up. That results in the AOD UI being shown while we wake
+                // up, with unpredictable consequences.
+                if (!powerManager.isInteractive) {
+                    aodUiAnimationPlaying = true
 
-                // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
-                statusBar.notificationPanelViewController.showAodUi()
+                    // Show AOD. That'll cause the KeyguardVisibilityHelper to call
+                    // #animateInKeyguard.
+                    statusBar.notificationPanelViewController.showAodUi()
+                }
             }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
 
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 977fe9c..f5364b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -71,7 +71,6 @@
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -152,7 +151,6 @@
             KeyguardStateController keyguardStateController,
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
@@ -250,7 +248,6 @@
                 keyguardStateController,
                 headsUpManagerPhone,
                 dynamicPrivacyController,
-                bypassHeadsUpNotifier,
                 falsingManager,
                 falsingCollector,
                 broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 9587261..3084a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -184,6 +184,7 @@
         entry.setHeadsUp(false);
         setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
         EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
+        mLogger.logNotificationActuallyRemoved(entry.getKey());
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
@@ -378,10 +379,6 @@
     public void onDensityOrFontScaleChanged() {
     }
 
-    public boolean isEntryAutoHeadsUpped(String key) {
-        return false;
-    }
-
     /**
      * Determines if the notification is for a critical call that must display on top of an active
      * input notification.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 2bdf62bb..6a74ba9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -73,6 +73,14 @@
         })
     }
 
+    fun logNotificationActuallyRemoved(key: String) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+        }, {
+            "notification removed $str1 "
+        })
+    }
+
     fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
         buffer.log(TAG, INFO, {
             str1 = key
@@ -83,11 +91,12 @@
         })
     }
 
-    fun logUpdateEntry(updatePostTime: Boolean) {
+    fun logUpdateEntry(key: String, updatePostTime: Boolean) {
         buffer.log(TAG, INFO, {
+            str1 = key
             bool1 = updatePostTime
         }, {
-            "update entry updatePostTime: $bool1"
+            "update entry $key updatePostTime: $bool1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 7bf1601..050b670 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -50,13 +50,6 @@
     boolean canDismissLockScreen();
 
     /**
-     * Whether we can currently perform the shared element SmartSpace transition. This is true if
-     * we're on the lockscreen, it can be dismissed with a swipe, and the Launcher is underneath the
-     * keyguard and displaying a SmartSpace that it has registered with System UI.
-     */
-    boolean canPerformSmartSpaceTransition();
-
-    /**
      * Whether the keyguard is allowed to rotate, or needs to be locked to the default orientation.
      */
     boolean isKeyguardScreenRotationAllowed();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 05a586b..978564f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -35,7 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -44,6 +44,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  */
 @SysUISingleton
@@ -58,7 +60,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
             new UpdateMonitorCallback();
-    private final SmartspaceTransitionController mSmartspaceTransitionController;
+    private final Lazy<KeyguardUnlockAnimationController> mUnlockAnimationControllerLazy;
 
     private boolean mCanDismissLockScreen;
     private boolean mShowing;
@@ -105,13 +107,13 @@
             Context context,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             LockPatternUtils lockPatternUtils,
-            SmartspaceTransitionController smartspaceTransitionController,
+            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             DumpManager dumpManager) {
         mContext = context;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-        mSmartspaceTransitionController = smartspaceTransitionController;
+        mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -249,12 +251,6 @@
     }
 
     @Override
-    public boolean canPerformSmartSpaceTransition() {
-        return canDismissLockScreen()
-                && mSmartspaceTransitionController.isSmartspaceTransitionPossible();
-    }
-
-    @Override
     public boolean isKeyguardScreenRotationAllowed() {
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
                 || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 74e0f40..c2439df 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,7 +48,6 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -96,9 +95,8 @@
 
     @Mock
     Resources mResources;
-    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
-    SmartspaceTransitionController mSmartSpaceTransitionController;
+    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
     private ClockPlugin mClockPlugin;
     @Mock
@@ -154,7 +152,6 @@
                 mBypassController,
                 mSmartspaceController,
                 mKeyguardUnlockAnimationController,
-                mSmartSpaceTransitionController,
                 mSecureSettings,
                 mExecutor,
                 mResources
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 80de248..217092e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -24,7 +24,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.communal.CommunalStateController;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -61,8 +60,6 @@
     @Mock
     KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock
-    SmartspaceTransitionController mSmartSpaceTransitionController;
-    @Mock
     ScreenOffAnimationController mScreenOffAnimationController;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -83,7 +80,6 @@
                 mConfigurationController,
                 mDozeParameters,
                 mKeyguardUnlockAnimationController,
-                mSmartSpaceTransitionController,
                 mScreenOffAnimationController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 7c5f57f..7af039b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -27,15 +27,15 @@
 import android.content.res.Resources;
 import android.os.Handler;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.complication.dagger.ComplicationHostViewComponent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +64,15 @@
     DreamOverlayContainerView mDreamOverlayContainerView;
 
     @Mock
+    ComplicationHostViewController mComplicationHostViewController;
+
+    @Mock
+    ComplicationHostViewComponent.Factory mComplicationHostViewComponentFactory;
+
+    @Mock
+    ComplicationHostViewComponent mComplicationHostViewComponent;
+
+    @Mock
     ViewGroup mDreamOverlayContentView;
 
     @Mock
@@ -80,9 +89,14 @@
                         DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT);
         when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
         when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+        when(mComplicationHostViewComponentFactory.create())
+                .thenReturn(mComplicationHostViewComponent);
+        when(mComplicationHostViewComponent.getController())
+                .thenReturn(mComplicationHostViewController);
 
         mController = new DreamOverlayContainerViewController(
                 mDreamOverlayContainerView,
+                mComplicationHostViewComponentFactory,
                 mDreamOverlayContentView,
                 mDreamOverlayStatusBarViewController,
                 mHandler,
@@ -104,20 +118,6 @@
     }
 
     @Test
-    public void testAddOverlayAddsOverlayToContentView() {
-        View overlay = new View(getContext());
-        ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(100, 100);
-        mController.addOverlay(overlay, layoutParams);
-        verify(mDreamOverlayContentView).addView(overlay, layoutParams);
-    }
-
-    @Test
-    public void testRemoveAllOverlaysRemovesOverlaysFromContentView() {
-        mController.removeAllOverlays();
-        verify(mDreamOverlayContentView).removeAllViews();
-    }
-
-    @Test
     public void testOnViewAttachedRegistersComputeInsetsListener() {
         mController.onViewAttached();
         verify(mViewTreeObserver).addOnComputeInternalInsetsListener(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index c0b7271..6b156a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -28,12 +28,11 @@
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 
-import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -48,18 +47,21 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamOverlayServiceTest extends SysuiTestCase {
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
 
+    @Mock
+    LifecycleOwner mLifecycleOwner;
+
+    @Mock
+    LifecycleRegistry mLifecycleRegistry;
+
     @Rule
     public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
 
@@ -76,12 +78,6 @@
     WindowManagerImpl mWindowManager;
 
     @Mock
-    ComplicationProvider mProvider;
-
-    @Mock
-    DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
     DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
 
     @Mock
@@ -102,13 +98,18 @@
 
         when(mDreamOverlayComponent.getDreamOverlayContainerViewController())
                 .thenReturn(mDreamOverlayContainerViewController);
-        when(mDreamOverlayComponentFactory.create())
+        when(mDreamOverlayComponent.getLifecycleOwner())
+                .thenReturn(mLifecycleOwner);
+        when(mDreamOverlayComponent.getLifecycleRegistry())
+                .thenReturn(mLifecycleRegistry);
+        when(mDreamOverlayComponentFactory
+                .create(any(), any()))
                 .thenReturn(mDreamOverlayComponent);
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
         mService = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController, mDreamOverlayComponentFactory);
+                mDreamOverlayComponentFactory);
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
@@ -128,78 +129,6 @@
     }
 
     @Test
-    public void testAddingOverlayToDream() throws Exception {
-        // Add overlay.
-        mService.addComplication(mProvider);
-        mMainExecutor.runAllReady();
-
-        final ArgumentCaptor<ComplicationHost.CreationCallback> creationCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.CreationCallback.class);
-        final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
-
-        // Ensure overlay provider is asked to create view.
-        verify(mProvider).onCreateComplication(any(), creationCallbackCapture.capture(),
-                interactionCallbackCapture.capture());
-        mMainExecutor.runAllReady();
-
-        // Inform service of overlay view creation.
-        final View view = new View(mContext);
-        final ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
-        );
-        creationCallbackCapture.getValue().onCreated(view, lp);
-        mMainExecutor.runAllReady();
-
-        // Verify that DreamOverlayContainerViewController is asked to add an overlay for the view.
-        verify(mDreamOverlayContainerViewController).addOverlay(view, lp);
-    }
-
-    @Test
-    public void testDreamOverlayExit() throws Exception {
-        // Add overlay.
-        mService.addComplication(mProvider);
-        mMainExecutor.runAllReady();
-
-        // Capture interaction callback from overlay creation.
-        final ArgumentCaptor<ComplicationHost.InteractionCallback> interactionCallbackCapture =
-                ArgumentCaptor.forClass(ComplicationHost.InteractionCallback.class);
-        verify(mProvider).onCreateComplication(any(), any(), interactionCallbackCapture.capture());
-
-        // Ask service to exit.
-        interactionCallbackCapture.getValue().onExit();
-        mMainExecutor.runAllReady();
-
-        // Ensure service informs dream host of exit.
-        verify(mDreamOverlayCallback).onExitRequested();
-    }
-
-    @Test
-    public void testListenerRegisteredWithDreamOverlayStateController() {
-        // Verify overlay service registered as listener with DreamOverlayStateController
-        // and inform callback of addition.
-        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-
-        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
-        when(mDreamOverlayStateController.getComplications()).thenReturn(Arrays.asList(mProvider));
-        callbackCapture.getValue().onComplicationsChanged();
-        mMainExecutor.runAllReady();
-
-        // Verify provider is asked to create overlay.
-        verify(mProvider).onCreateComplication(any(), any(), any());
-    }
-
-    @Test
-    public void testOnDestroyRemovesOverlayStateCallback() {
-        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
-        mService.onDestroy();
-        verify(mDreamOverlayStateController).removeCallback(callbackCapture.getValue());
-    }
-
-    @Test
     public void testShouldShowComplicationsTrueByDefault() {
         assertThat(mService.shouldShowComplications()).isTrue();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index efc3c7c..7d0833d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -27,11 +27,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,7 +46,7 @@
     DreamOverlayStateController.Callback mCallback;
 
     @Mock
-    ComplicationProvider mProvider;
+    Complication mComplication;
 
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -63,24 +62,23 @@
         stateController.addCallback(mCallback);
 
         // Add complication and verify callback is notified.
-        final ListenableFuture<DreamOverlayStateController.ComplicationToken> tokenFuture =
-                stateController.addComplication(mProvider);
+        stateController.addComplication(mComplication);
 
         mExecutor.runAllReady();
 
         verify(mCallback, times(1)).onComplicationsChanged();
 
-        final Collection<ComplicationProvider> providers = stateController.getComplications();
-        assertEquals(providers.size(), 1);
-        assertTrue(providers.contains(mProvider));
+        final Collection<Complication> complications = stateController.getComplications();
+        assertEquals(complications.size(), 1);
+        assertTrue(complications.contains(mComplication));
 
         clearInvocations(mCallback);
 
         // Remove complication and verify callback is notified.
-        stateController.removeComplication(tokenFuture.get());
+        stateController.removeComplication(mComplication);
         mExecutor.runAllReady();
         verify(mCallback, times(1)).onComplicationsChanged();
-        assertTrue(providers.isEmpty());
+        assertTrue(stateController.getComplications().isEmpty());
     }
 
     @Test
@@ -88,7 +86,7 @@
         final DreamOverlayStateController stateController =
                 new DreamOverlayStateController(mExecutor);
 
-        stateController.addComplication(mProvider);
+        stateController.addComplication(mComplication);
         mExecutor.runAllReady();
 
         // Verify callback occurs on add when an overlay is already present.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
new file mode 100644
index 0000000..afc0309d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
+    @Before
+    public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
+    }
+
+    @Test
+    /**
+     * Ensures registration and callback lifecycles are respected.
+     */
+    public void testLifecycle() {
+        getContext().getMainExecutor().execute(() -> {
+            final DreamOverlayStateController stateController =
+                    Mockito.mock(DreamOverlayStateController.class);
+            final ComplicationCollectionLiveData liveData =
+                    new ComplicationCollectionLiveData(stateController);
+            final HashSet<Complication> complications = new HashSet<>();
+            final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
+            complications.add(Mockito.mock(Complication.class));
+
+            when(stateController.getComplications()).thenReturn(complications);
+
+            liveData.observeForever(observer);
+            ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+                    ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+
+            verify(stateController).addCallback(callbackCaptor.capture());
+            verifyUpdate(observer, complications);
+
+            complications.add(Mockito.mock(Complication.class));
+            callbackCaptor.getValue().onComplicationsChanged();
+
+            verifyUpdate(observer, complications);
+        });
+    }
+
+    void verifyUpdate(Observer<Collection<Complication>> observer,
+            Collection<Complication> targetCollection) {
+        ArgumentCaptor<Collection<Complication>> collectionCaptor =
+                ArgumentCaptor.forClass(Collection.class);
+
+        verify(observer).onChanged(collectionCaptor.capture());
+
+        assertThat(collectionCaptor.getValue().equals(targetCollection)).isTrue();
+        Mockito.clearInvocations(observer);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
new file mode 100644
index 0000000..3b9e398
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationHostViewControllerTest extends SysuiTestCase {
+    @Mock
+    ConstraintLayout mComplicationHostView;
+
+    @Mock
+    LifecycleOwner mLifecycleOwner;
+
+    @Mock
+    LiveData<Collection<ComplicationViewModel>> mComplicationViewModelLiveData;
+
+    @Mock
+    ComplicationCollectionViewModel mViewModel;
+
+    @Mock
+    ComplicationViewModel mComplicationViewModel;
+
+    @Mock
+    ComplicationLayoutEngine mLayoutEngine;
+
+    @Mock
+    ComplicationId mComplicationId;
+
+    @Mock
+    Complication mComplication;
+
+    @Mock
+    Complication.ViewHolder mViewHolder;
+
+    @Mock
+    View mComplicationView;
+
+    @Mock
+    ComplicationLayoutParams mComplicationLayoutParams;
+
+    @Complication.Category
+    static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mViewModel.getComplications()).thenReturn(mComplicationViewModelLiveData);
+        when(mComplicationViewModel.getId()).thenReturn(mComplicationId);
+        when(mComplicationViewModel.getComplication()).thenReturn(mComplication);
+        when(mComplication.createView(eq(mComplicationViewModel))).thenReturn(mViewHolder);
+        when(mViewHolder.getView()).thenReturn(mComplicationView);
+        when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
+        when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
+        when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+    }
+
+    /**
+     * Ensures the lifecycle of complications is properly handled.
+     */
+    @Test
+    public void testViewModelObservation() {
+        final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
+                ArgumentCaptor.forClass(Observer.class);
+        final ComplicationHostViewController controller = new ComplicationHostViewController(
+                mComplicationHostView,
+                mLayoutEngine,
+                mLifecycleOwner,
+                mViewModel);
+
+        controller.init();
+
+        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+                observerArgumentCaptor.capture());
+
+        final Observer<Collection<ComplicationViewModel>> observer =
+                observerArgumentCaptor.getValue();
+
+        // Add complication and ensure it is added to the view.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Arrays.asList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
+                eq(mComplicationLayoutParams), eq(COMPLICATION_CATEGORY));
+
+        // Remove complication and ensure it is removed from the view by id.
+        observer.onChanged(new HashSet<>());
+
+        verify(mLayoutEngine).removeComplication(eq(mComplicationId));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
new file mode 100644
index 0000000..f227a9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutEngineTest extends SysuiTestCase {
+    @Mock
+    ConstraintLayout mLayout;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private static class ViewInfo {
+        private static int sNextId = 1;
+        public final ComplicationId id;
+        public final View view;
+        public final ComplicationLayoutParams lp;
+
+        @Complication.Category
+        public final int category;
+
+        private static ComplicationId.Factory sFactory = new ComplicationId.Factory();
+
+        ViewInfo(ComplicationLayoutParams params, @Complication.Category int category,
+                ConstraintLayout layout) {
+            this.lp = params;
+            this.category = category;
+            this.view = Mockito.mock(View.class);
+            this.id = sFactory.getNextId();
+            when(view.getId()).thenReturn(sNextId++);
+            when(view.getParent()).thenReturn(layout);
+        }
+
+        void clearInvocations() {
+            Mockito.clearInvocations(view);
+        }
+    }
+
+    private void verifyChange(ViewInfo viewInfo,
+            boolean verifyAdd,
+            Consumer<ConstraintLayout.LayoutParams> paramConsumer) {
+        ArgumentCaptor<ConstraintLayout.LayoutParams> lpCaptor =
+                ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
+        verify(viewInfo.view).setLayoutParams(lpCaptor.capture());
+
+        if (verifyAdd) {
+            verify(mLayout).addView(eq(viewInfo.view));
+        }
+
+        ConstraintLayout.LayoutParams capturedParams = lpCaptor.getValue();
+        paramConsumer.accept(capturedParams);
+    }
+
+    private void addComplication(ComplicationLayoutEngine engine, ViewInfo info) {
+        engine.addComplication(info.id, info.view, info.lp, info.category);
+    }
+
+    /**
+     * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
+     */
+    @Test
+    public void testSingleLayout() {
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                        | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+        addComplication(engine, firstViewInfo);
+
+        // Ensure the view is added to the top end corner
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular direction updates.
+     */
+    @Test
+    public void testDirectionLayout() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+
+        firstViewInfo.clearInvocations();
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+        // The first added view should now be underneath the second view.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The second view should be in the top position.
+        verifyChange(secondViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular position updates.
+     */
+    @Test
+    public void testPositionLayout() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+        firstViewInfo.clearInvocations();
+        secondViewInfo.clearInvocations();
+
+        final ViewInfo thirdViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, thirdViewInfo);
+
+        // The first added view should now be underneath the second view.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == secondViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The second view should be in underneath the third view.
+        verifyChange(secondViewInfo, false, lp -> {
+            assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        // The third view should be in at the top.
+        verifyChange(thirdViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+
+        final ViewInfo fourthViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_START,
+                        1),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, fourthViewInfo);
+
+        verifyChange(fourthViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
+        });
+    }
+
+    /**
+     * Ensures layout in a particular position updates.
+     */
+    @Test
+    public void testRemoval() {
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        engine.addComplication(firstViewInfo.id, firstViewInfo.view, firstViewInfo.lp,
+                firstViewInfo.category);
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        engine.addComplication(secondViewInfo.id, secondViewInfo.view, secondViewInfo.lp,
+                secondViewInfo.category);
+
+        firstViewInfo.clearInvocations();
+
+        engine.removeComplication(secondViewInfo.id);
+        verify(mLayout).removeView(eq(secondViewInfo.view));
+
+        verifyChange(firstViewInfo, true, lp -> {
+            assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+            assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
+        });
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
new file mode 100644
index 0000000..d080bbc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationLayoutParamsTest extends SysuiTestCase {
+    /**
+     * Ensures ComplicationLayoutParams cannot be constructed with improper position or direction.
+     */
+    @Test
+    public void testPositionValidation() {
+        final HashSet<Integer> invalidCombinations = new HashSet(Arrays.asList(
+                ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.POSITION_END | ComplicationLayoutParams.POSITION_START
+        ));
+
+        final int allPositions = ComplicationLayoutParams.POSITION_TOP
+                | ComplicationLayoutParams.POSITION_START
+                | ComplicationLayoutParams.POSITION_END
+                | ComplicationLayoutParams.POSITION_BOTTOM;
+
+        final HashSet<Integer> allDirections = new HashSet(Arrays.asList(
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                ComplicationLayoutParams.DIRECTION_UP,
+                ComplicationLayoutParams.DIRECTION_START,
+                ComplicationLayoutParams.DIRECTION_END
+        ));
+
+        final HashMap<Integer, Integer> invalidDirections = new HashMap<>();
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_DOWN,
+                ComplicationLayoutParams.POSITION_BOTTOM);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_UP,
+                ComplicationLayoutParams.POSITION_TOP);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_START,
+                ComplicationLayoutParams.POSITION_START);
+        invalidDirections.put(ComplicationLayoutParams.DIRECTION_END,
+                ComplicationLayoutParams.POSITION_END);
+
+
+        for (int position = 0; position <= allPositions; ++position) {
+            boolean properPosition = position != 0;
+            if (properPosition) {
+                for (Integer combination : invalidCombinations) {
+                    if ((combination & position) == combination) {
+                        properPosition = false;
+                    }
+                }
+            }
+            boolean exceptionEncountered = false;
+            for (Integer direction : allDirections) {
+                final int invalidPosition = invalidDirections.get(direction);
+                final boolean properDirection = (invalidPosition & position) != invalidPosition;
+
+                try {
+                    final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                            100,
+                            100,
+                            position,
+                            direction,
+                            0);
+                } catch (Exception e) {
+                    exceptionEncountered = true;
+                }
+
+                assertThat((properPosition && properDirection) || exceptionEncountered).isTrue();
+            }
+        }
+    }
+
+    /**
+     * Ensures ComplicationLayoutParams is properly duplicated on copy construction.
+     */
+    @Test
+    public void testCopyConstruction() {
+        final ComplicationLayoutParams params = new ComplicationLayoutParams(
+                100,
+                100,
+                ComplicationLayoutParams.POSITION_TOP,
+                ComplicationLayoutParams.DIRECTION_DOWN,
+                3);
+        final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
+
+        assertThat(copy.getDirection() == params.getDirection()).isTrue();
+        assertThat(copy.getPosition() == params.getPosition()).isTrue();
+        assertThat(copy.getWeight() == params.getWeight()).isTrue();
+        assertThat(copy.height == params.height).isTrue();
+        assertThat(copy.width == params.width).isTrue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
new file mode 100644
index 0000000..2bc427d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationViewModelTransformerTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.dagger.ComplicationViewModelComponent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ComplicationViewModelTransformerTest extends SysuiTestCase {
+    @Mock
+    ComplicationViewModelComponent.Factory mFactory;
+
+    @Mock
+    ComplicationViewModelComponent mComponent;
+
+    @Mock
+    ComplicationViewModelProvider mViewModelProvider;
+
+    @Mock
+    ComplicationViewModel mViewModel;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent);
+        when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider);
+        when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel);
+    }
+
+    /**
+     * Ensure the same id is returned for the same complication across invocations.
+     */
+    @Test
+    public void testStableIds() {
+        final ComplicationViewModelTransformer transformer =
+                new ComplicationViewModelTransformer(mFactory);
+
+        final Complication complication = Mockito.mock(Complication.class);
+
+        ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+        transformer.getViewModel(complication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId firstId = idCaptor.getValue();
+
+        Mockito.clearInvocations(mFactory);
+
+        transformer.getViewModel(complication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId secondId = idCaptor.getValue();
+
+        assertEquals(secondId, firstId);
+    }
+
+    /**
+     * Ensure unique ids are assigned to different complications.
+     */
+    @Test
+    public void testUniqueIds() {
+        final ComplicationViewModelTransformer transformer =
+                new ComplicationViewModelTransformer(mFactory);
+
+        final Complication firstComplication = Mockito.mock(Complication.class);
+        final Complication secondComplication = Mockito.mock(Complication.class);
+
+        ArgumentCaptor<ComplicationId> idCaptor = ArgumentCaptor.forClass(ComplicationId.class);
+
+        transformer.getViewModel(firstComplication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId firstId = idCaptor.getValue();
+
+        Mockito.clearInvocations(mFactory);
+
+        transformer.getViewModel(secondComplication);
+        verify(mFactory).create(Mockito.any(), idCaptor.capture());
+        final ComplicationId secondId = idCaptor.getValue();
+
+        assertNotEquals(secondId, firstId);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index f3043e9..fb1a968 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,7 +14,6 @@
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import junit.framework.Assert.assertEquals
@@ -44,8 +43,6 @@
     @Mock
     private lateinit var keyguardViewController: KeyguardViewController
     @Mock
-    private lateinit var smartspaceTransitionController: SmartspaceTransitionController
-    @Mock
     private lateinit var featureFlags: FeatureFlags
     @Mock
     private lateinit var biometricUnlockController: BiometricUnlockController
@@ -59,7 +56,7 @@
         MockitoAnnotations.initMocks(this)
         keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
             context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
-            smartspaceTransitionController, featureFlags, biometricUnlockController
+            featureFlags, { biometricUnlockController }
         )
 
         `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
@@ -87,7 +84,7 @@
     fun noSurfaceAnimation_ifWakeAndUnlocking() {
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
 
-        keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+        keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
             remoteAnimationTarget,
             0 /* startTime */,
             false /* requestedShowSurfaceBehindKeyguard */
@@ -118,15 +115,12 @@
     fun surfaceAnimation_ifNotWakeAndUnlocking() {
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
 
-        keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+        keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
             remoteAnimationTarget,
             0 /* startTime */,
             false /* requestedShowSurfaceBehindKeyguard */
         )
 
-        // Make sure the animator was started.
-        assertTrue(keyguardUnlockAnimationController.surfaceBehindEntryAnimator.isRunning)
-
         // Since the animation is running, we should not have finished the remote animation.
         verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished(
             false /* cancelled */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
index 4839bde..81ae209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -21,18 +21,17 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
-import com.android.systemui.media.taptotransfer.sender.*
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService
 import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.anyString
@@ -44,6 +43,7 @@
 import java.util.concurrent.Executor
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttCommandLineHelperTest : SysuiTestCase() {
 
     private val inlineExecutor = Executor { command -> command.run() }
@@ -53,11 +53,9 @@
     private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper
 
     @Mock
-    private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender
-    @Mock
     private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver
     @Mock
-    private lateinit var mediaSenderService: IDeviceSenderCallback.Stub
+    private lateinit var mediaSenderService: IDeviceSenderService.Stub
     private lateinit var mediaSenderServiceComponentName: ComponentName
 
     @Before
@@ -73,28 +71,15 @@
             MediaTttCommandLineHelper(
                 commandRegistry,
                 context,
-                mediaTttChipControllerSender,
                 mediaTttChipControllerReceiver,
-                FakeExecutor(FakeSystemClock())
             )
     }
 
     @Test(expected = IllegalStateException::class)
-    fun constructor_addSenderCommandAlreadyRegistered() {
-        // Since creating the chip controller should automatically register the add command, it
+    fun constructor_senderCommandAlreadyRegistered() {
+        // Since creating the chip controller should automatically register the sender command, it
         // should throw when registering it again.
-        commandRegistry.registerCommand(
-            ADD_CHIP_COMMAND_SENDER_TAG
-        ) { EmptyCommand() }
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun constructor_removeSenderCommandAlreadyRegistered() {
-        // Since creating the chip controller should automatically register the remove command, it
-        // should throw when registering it again.
-        commandRegistry.registerCommand(
-            REMOVE_CHIP_COMMAND_SENDER_TAG
-        ) { EmptyCommand() }
+        commandRegistry.registerCommand(SENDER_COMMAND) { EmptyCommand() }
     }
 
     @Test(expected = IllegalStateException::class)
@@ -127,24 +112,74 @@
     }
 
     @Test
-    fun sender_transferInitiated_chipDisplayWithCorrectState() {
-        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+    fun sender_moveCloserToEndCast_serviceCallbackCalled() {
+        commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand())
 
-        verify(mediaTttChipControllerSender).displayChip(any(TransferInitiated::class.java))
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+        val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+        verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor))
+        assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
     }
 
     @Test
-    fun sender_transferSucceeded_chipDisplayWithCorrectState() {
-        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+    fun sender_transferToReceiverTriggered_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferToReceiverTriggeredCommand())
 
-        verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java))
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+        val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+        verify(mediaSenderService).transferToReceiverTriggered(any(), capture(deviceInfoCaptor))
+        assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
     }
 
     @Test
-    fun sender_removeCommand_chipRemoved() {
-        commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_SENDER_TAG))
+    fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand())
 
-        verify(mediaTttChipControllerSender).removeChip()
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+        verify(mediaSenderService).transferToThisDeviceTriggered(any(), any())
+    }
+
+    @Test
+    fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand())
+
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+        val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+        verify(mediaSenderService)
+            .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any())
+        assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand())
+
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+        val deviceInfoCaptor = argumentCaptor<DeviceInfo>()
+        verify(mediaSenderService)
+            .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any())
+        assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+    }
+
+    @Test
+    fun sender_transferFailed_serviceCallbackCalled() {
+        commandRegistry.onShellCommand(pw, getTransferFailedCommand())
+
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+        verify(mediaSenderService).transferFailed(any(), any())
+    }
+
+    @Test
+    fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() {
+        commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand())
+
+        // Once we're no longer close to the receiver, we should unbind the service.
+        assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse()
+        verify(mediaSenderService).noLongerCloseToReceiver(any(), any())
     }
 
     @Test
@@ -163,23 +198,58 @@
 
     private fun getMoveCloserToStartCastCommand(): Array<String> =
         arrayOf(
-            ADD_CHIP_COMMAND_SENDER_TAG,
+            SENDER_COMMAND,
             DEVICE_NAME,
             MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
         )
 
-    private fun getTransferInitiatedCommand(): Array<String> =
+    private fun getMoveCloserToEndCastCommand(): Array<String> =
         arrayOf(
-            ADD_CHIP_COMMAND_SENDER_TAG,
+            SENDER_COMMAND,
             DEVICE_NAME,
-            TRANSFER_INITIATED_COMMAND_NAME
+            MOVE_CLOSER_TO_END_CAST_COMMAND_NAME
         )
 
-    private fun getTransferSucceededCommand(): Array<String> =
+    private fun getTransferToReceiverTriggeredCommand(): Array<String> =
         arrayOf(
-            ADD_CHIP_COMMAND_SENDER_TAG,
+            SENDER_COMMAND,
             DEVICE_NAME,
-            TRANSFER_SUCCEEDED_COMMAND_NAME
+            TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME
+        )
+
+    private fun getTransferToThisDeviceTriggeredCommand(): Array<String> =
+        arrayOf(
+            SENDER_COMMAND,
+            DEVICE_NAME,
+            TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME
+        )
+
+    private fun getTransferToReceiverSucceededCommand(): Array<String> =
+        arrayOf(
+            SENDER_COMMAND,
+            DEVICE_NAME,
+            TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME
+        )
+
+    private fun getTransferToThisDeviceSucceededCommand(): Array<String> =
+        arrayOf(
+            SENDER_COMMAND,
+            DEVICE_NAME,
+            TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME
+        )
+
+    private fun getTransferFailedCommand(): Array<String> =
+        arrayOf(
+            SENDER_COMMAND,
+            DEVICE_NAME,
+            TRANSFER_FAILED_COMMAND_NAME
+        )
+
+    private fun getNoLongerCloseToReceiverCommand(): Array<String> =
+        arrayOf(
+            SENDER_COMMAND,
+            DEVICE_NAME,
+            NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME
         )
 
     class EmptyCommand : Command {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 927ca7a..242fd19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
@@ -38,6 +39,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerCommonTest : SysuiTestCase() {
     private lateinit var controllerCommon: MediaTttChipControllerCommon<MediaTttChipState>
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index afaab80..1d1265b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
@@ -34,6 +35,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerReceiverTest : SysuiTestCase() {
     private lateinit var controllerReceiver: MediaTttChipControllerReceiver
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index ecc4c46..6b4eebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -26,26 +26,21 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.SettableFuture
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Future
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttChipControllerSenderTest : SysuiTestCase() {
     private lateinit var appIconDrawable: Drawable
-    private lateinit var fakeMainClock: FakeSystemClock
-    private lateinit var fakeMainExecutor: FakeExecutor
-    private lateinit var fakeBackgroundClock: FakeSystemClock
-    private lateinit var fakeBackgroundExecutor: FakeExecutor
 
     private lateinit var controllerSender: MediaTttChipControllerSender
 
@@ -56,124 +51,92 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         appIconDrawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context)
-        fakeMainClock = FakeSystemClock()
-        fakeMainExecutor = FakeExecutor(fakeMainClock)
-        fakeBackgroundClock = FakeSystemClock()
-        fakeBackgroundExecutor = FakeExecutor(fakeBackgroundClock)
-        controllerSender = MediaTttChipControllerSender(
-            context, windowManager, fakeMainExecutor, fakeBackgroundExecutor
-        )
+        controllerSender = MediaTttChipControllerSender(context, windowManager)
     }
 
     @Test
-    fun moveCloserToStartCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
-        controllerSender.displayChip(moveCloserToStartCast())
+    fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+        val state = moveCloserToStartCast()
+        controllerSender.displayChip(state)
 
         val chipView = getChipView()
         assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
         assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
-        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
-        val future: SettableFuture<Runnable?> = SettableFuture.create()
-        controllerSender.displayChip(transferInitiated(future))
+    fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() {
+        val state = moveCloserToEndCast()
+        controllerSender.displayChip(state)
 
-        // Don't resolve the future in any way and don't run our executors
-
-        // Assert we're still in the loading state
         val chipView = getChipView()
         assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
         assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
-        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+        val state = transferToReceiverTriggered()
+        controllerSender.displayChip(state)
+
+        val chipView = getChipView()
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferInitiated_futureResolvedSuccessfully_switchesToTransferSucceeded() {
-        val future: SettableFuture<Runnable?> = SettableFuture.create()
-        val undoRunnable = Runnable { }
-
-        controllerSender.displayChip(transferInitiated(future))
-
-        future.set(undoRunnable)
-        fakeBackgroundExecutor.advanceClockToLast()
-        fakeBackgroundExecutor.runAllReady()
-        fakeMainExecutor.advanceClockToLast()
-        val numRun = fakeMainExecutor.runAllReady()
-
-        // Assert we ran the future callback
-        assertThat(numRun).isEqualTo(1)
-        // Assert that we've moved to the successful state
-        val chipView = getChipView()
-        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun transferInitiated_futureCancelled_chipRemoved() {
-        val future: SettableFuture<Runnable?> = SettableFuture.create()
-
-        controllerSender.displayChip(transferInitiated(future))
-
-        future.cancel(true)
-        fakeBackgroundExecutor.advanceClockToLast()
-        fakeBackgroundExecutor.runAllReady()
-        fakeMainExecutor.advanceClockToLast()
-        val numRun = fakeMainExecutor.runAllReady()
-
-        // Assert we ran the future callback
-        assertThat(numRun).isEqualTo(1)
-        // Assert that we've hidden the chip
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
-        val future: SettableFuture<Runnable?> = SettableFuture.create()
-        controllerSender.displayChip(transferInitiated(future))
-
-        // We won't set anything on the future, but we will still run the executors so that we're
-        // waiting on the future resolving. If we have a bug in our code, then this test will time
-        // out because we're waiting on the future indefinitely.
-        fakeBackgroundExecutor.advanceClockToLast()
-        fakeBackgroundExecutor.runAllReady()
-        fakeMainExecutor.advanceClockToLast()
-        val numRun = fakeMainExecutor.runAllReady()
-
-        // Assert we eventually decide to not wait for the future anymore
-        assertThat(numRun).isEqualTo(1)
-        // Assert we've hidden the chip
-        verify(windowManager).removeView(any())
-    }
-
-    @Test
-    fun transferSucceeded_appIcon_chipTextContainsDeviceName_noLoadingIcon() {
-        controllerSender.displayChip(transferSucceeded())
+    fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() {
+        val state = transferToThisDeviceTriggered()
+        controllerSender.displayChip(state)
 
         val chipView = getChipView()
         assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
         assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
-        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
-        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferSucceededNullUndoRunnable_noUndo() {
-        controllerSender.displayChip(transferSucceeded(undoRunnable = null))
+    fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+        val state = transferToReceiverSucceeded()
+        controllerSender.displayChip(state)
+
+        val chipView = getChipView()
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() {
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback = null))
 
         val chipView = getChipView()
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferSucceededWithUndoRunnable_undoWithClick() {
-        controllerSender.displayChip(transferSucceeded { })
+    fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() {
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
 
         val chipView = getChipView()
         assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
@@ -181,48 +144,154 @@
     }
 
     @Test
-    fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() {
-        var runnableRun = false
-        val runnable = Runnable { runnableRun = true }
+    fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
 
-        controllerSender.displayChip(transferSucceeded(undoRunnable = runnable))
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
         getChipView().getUndoButton().performClick()
 
-        assertThat(runnableRun).isTrue()
+        assertThat(undoCallbackCalled).isTrue()
     }
 
     @Test
-    fun changeFromCloserToStartToTransferInitiated_loadingIconAppears() {
+    fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
+
+        getChipView().getUndoButton().performClick()
+
+        assertThat(getChipView().getChipText())
+            .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context))
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() {
+        val state = transferToThisDeviceSucceeded()
+        controllerSender.displayChip(state)
+
+        val chipView = getChipView()
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() {
+        controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback = null))
+
+        val chipView = getChipView()
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() {
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+
+        val chipView = getChipView()
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() {
+        var undoCallbackCalled = false
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {
+                undoCallbackCalled = true
+            }
+        }
+
+        controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(undoCallbackCalled).isTrue()
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() {
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback))
+
+        getChipView().getUndoButton().performClick()
+
+        assertThat(getChipView().getChipText())
+            .isEqualTo(transferToReceiverTriggered().getChipTextString(context))
+    }
+
+    @Test
+    fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
+        val state = transferFailed()
+        controllerSender.displayChip(state)
+
+        val chipView = getChipView()
+        assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable)
+        assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC)
+        assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context))
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+        assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun changeFromCloserToStartToTransferTriggered_loadingIconAppears() {
         controllerSender.displayChip(moveCloserToStartCast())
-        controllerSender.displayChip(transferInitiated())
+        controllerSender.displayChip(transferToReceiverTriggered())
 
         assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
     }
 
     @Test
-    fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
-        controllerSender.displayChip(transferInitiated())
-        controllerSender.displayChip(transferSucceeded())
+    fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() {
+        controllerSender.displayChip(transferToReceiverTriggered())
+        controllerSender.displayChip(transferToReceiverSucceeded())
 
         assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
     }
 
     @Test
-    fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
-        controllerSender.displayChip(transferInitiated())
-        controllerSender.displayChip(transferSucceeded { })
+    fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() {
+        controllerSender.displayChip(transferToReceiverTriggered())
+        controllerSender.displayChip(
+            transferToReceiverSucceeded(
+                object : IUndoTransferCallback.Stub() {
+                    override fun onUndoTriggered() {}
+                }
+            )
+        )
 
         assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
     }
 
     @Test
     fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() {
-        controllerSender.displayChip(transferSucceeded())
+        controllerSender.displayChip(transferToReceiverSucceeded())
         controllerSender.displayChip(moveCloserToStartCast())
 
         assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun changeFromTransferTriggeredToTransferFailed_failureIconAppears() {
+        controllerSender.displayChip(transferToReceiverTriggered())
+        controllerSender.displayChip(transferFailed())
+
+        assertThat(getChipView().getFailureIcon().visibility).isEqualTo(View.VISIBLE)
+    }
+
     private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
 
     private fun LinearLayout.getChipText(): String =
@@ -233,6 +302,8 @@
 
     private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo)
 
+    private fun LinearLayout.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
+
     private fun getChipView(): LinearLayout {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
         verify(windowManager).addView(viewCaptor.capture(), any())
@@ -244,18 +315,32 @@
         MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferInitiated(
-        future: Future<Runnable?> = TEST_FUTURE
-    ) = TransferInitiated(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, future)
+    private fun moveCloserToEndCast() =
+        MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
 
     /** Helper method providing default parameters to not clutter up the tests. */
-    private fun transferSucceeded(
-        undoRunnable: Runnable? = null
-    ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable)
+    private fun transferToReceiverTriggered() =
+        TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+
+    /** Helper method providing default parameters to not clutter up the tests. */
+    private fun transferToThisDeviceTriggered() =
+        TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC)
+
+    /** Helper method providing default parameters to not clutter up the tests. */
+    private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) =
+        TransferToReceiverSucceeded(
+            appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+        )
+
+    /** Helper method providing default parameters to not clutter up the tests. */
+    private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) =
+        TransferToThisDeviceSucceeded(
+            appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback
+        )
+
+    /** Helper method providing default parameters to not clutter up the tests. */
+    private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC)
 }
 
 private const val DEVICE_NAME = "My Tablet"
 private const val APP_ICON_CONTENT_DESC = "Content description"
-// Use a settable future that hasn't yet been set so that we don't immediately switch to the success
-// state.
-private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
index 8f64698..64542cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt
@@ -4,21 +4,24 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shared.mediattt.DeviceInfo
-import com.android.systemui.shared.mediattt.IDeviceSenderCallback
+import com.android.systemui.shared.mediattt.IDeviceSenderService
+import com.android.systemui.shared.mediattt.IUndoTransferCallback
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@Ignore("b/216286227")
 class MediaTttSenderServiceTest : SysuiTestCase() {
 
-    private lateinit var service: MediaTttSenderService
-    private lateinit var callback: IDeviceSenderCallback
+    private lateinit var service: IDeviceSenderService
 
     @Mock
     private lateinit var controller: MediaTttChipControllerSender
@@ -30,19 +33,94 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        service = MediaTttSenderService(context, controller)
-        callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null))
+        val mediaTttSenderService = MediaTttSenderService(context, controller)
+        service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null))
     }
 
     @Test
-    fun closeToReceiverToStartCast_controllerTriggeredWithMoveCloserToStartCastState() {
+    fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() {
         val name = "Fake name"
-        callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
+        service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name))
 
         val chipStateCaptor = argumentCaptor<MoveCloserToStartCast>()
         verify(controller).displayChip(capture(chipStateCaptor))
 
         val chipState = chipStateCaptor.value!!
-        assertThat(chipState.otherDeviceName).isEqualTo(name)
+        assertThat(chipState.getChipTextString(context)).contains(name)
+    }
+
+    @Test
+    fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() {
+        val name = "Fake name"
+        service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name))
+
+        val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>()
+        verify(controller).displayChip(capture(chipStateCaptor))
+
+        val chipState = chipStateCaptor.value!!
+        assertThat(chipState.getChipTextString(context)).contains(name)
+    }
+
+    @Test
+    fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() {
+        service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name"))
+
+        verify(controller).displayChip(any<TransferToThisDeviceTriggered>())
+    }
+
+    @Test
+    fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() {
+        val name = "Fake name"
+        service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name))
+
+        val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>()
+        verify(controller).displayChip(capture(chipStateCaptor))
+
+        val chipState = chipStateCaptor.value!!
+        assertThat(chipState.getChipTextString(context)).contains(name)
+    }
+
+    @Test
+    fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() {
+        val name = "Fake name"
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback)
+
+        val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>()
+        verify(controller).displayChip(capture(chipStateCaptor))
+
+        val chipState = chipStateCaptor.value!!
+        assertThat(chipState.getChipTextString(context)).contains(name)
+        assertThat(chipState.undoCallback).isEqualTo(undoCallback)
+    }
+
+    @Test
+    fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() {
+        val undoCallback = object : IUndoTransferCallback.Stub() {
+            override fun onUndoTriggered() {}
+        }
+        service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback)
+
+        val chipStateCaptor = argumentCaptor<TransferToThisDeviceSucceeded>()
+        verify(controller).displayChip(capture(chipStateCaptor))
+
+        val chipState = chipStateCaptor.value!!
+        assertThat(chipState.undoCallback).isEqualTo(undoCallback)
+    }
+
+    @Test
+    fun transferFailed_controllerTriggeredWithTransferFailedState() {
+        service.transferFailed(mediaInfo, DeviceInfo("Fake name"))
+
+        verify(controller).displayChip(any<TransferFailed>())
+    }
+
+    @Test
+    fun noLongerCloseToReceiver_controllerRemoveChipTriggered() {
+        service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name"))
+
+        verify(controller).removeChip()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0bce621..91e5d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -302,6 +302,40 @@
     }
 
     @Test
+    fun ignoreBlurForUnlock_ignores() {
+        notificationShadeDepthController.onPanelExpansionChanged(
+            rawFraction = 1f, expanded = true, tracking = false
+        )
+        `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
+
+        notificationShadeDepthController.blursDisabledForAppLaunch = false
+        notificationShadeDepthController.blursDisabledForUnlock = true
+
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+        // Since we are ignoring blurs for unlock, we should be applying blur = 0 despite setting it
+        // to maxBlur above.
+        verify(blurUtils).applyBlur(any(), eq(0), eq(false))
+    }
+
+    @Test
+    fun ignoreBlurForUnlock_doesNotIgnore() {
+        notificationShadeDepthController.onPanelExpansionChanged(
+            rawFraction = 1f, expanded = true, tracking = false
+        )
+        `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
+
+        notificationShadeDepthController.blursDisabledForAppLaunch = false
+        notificationShadeDepthController.blursDisabledForUnlock = false
+
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+        // Since we are not ignoring blurs for unlock (or app launch), we should apply the blur we
+        // returned above (maxBlur).
+        verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+    }
+
+    @Test
     fun brightnessMirrorVisible_whenVisible() {
         notificationShadeDepthController.brightnessMirrorVisible = true
         verify(brightnessSpring).animateTo(eq(maxBlur), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 1961ab2..188baaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -561,6 +561,10 @@
 
             override fun setMediaTarget(target: SmartspaceTarget?) {
             }
+
+            override fun getSelectedPage(): Int {
+                return -1
+            }
         })
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
index bbe92f6..15ff555 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
@@ -274,6 +274,18 @@
         public void removeChild(@NonNull NodeController child, boolean isTransfer) {
             view.removeView(child.getView());
         }
+
+        @Override
+        public void onViewAdded() {
+        }
+
+        @Override
+        public void onViewMoved() {
+        }
+
+        @Override
+        public void onViewRemoved() {
+        }
     }
 
     private static class SpecBuilder {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 04c6f6c..86a705f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -137,6 +137,7 @@
     @Mock private VisualStabilityManager mVisualStabilityManager;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
+    @Mock private StackStateLogger mStackLogger;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -191,7 +192,8 @@
                 mRemoteInputManager,
                 mVisualStabilityManager,
                 mShadeController,
-                mJankMonitor
+                mJankMonitor,
+                mStackLogger
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index c3349f1..5ca1f21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -104,6 +105,8 @@
     private ScreenLifecycle mScreenLifecycle;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -126,7 +129,7 @@
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
-                mAuthController, mStatusBarStateController);
+                mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 35f671bf..1c8b35a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -104,6 +104,7 @@
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.idle.IdleHostViewController;
 import com.android.systemui.idle.dagger.IdleViewComponent;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.media.KeyguardMediaController;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
@@ -362,6 +363,8 @@
     private QsFrameTranslateController mQsFrameTranslateController;
     @Mock
     private StatusBarWindowStateController mStatusBarWindowStateController;
+    @Mock
+    private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -546,7 +549,8 @@
                 mSysUIUnfoldComponent,
                 mControlsComponent,
                 mInteractionJankMonitor,
-                mQsFrameTranslateController);
+                mQsFrameTranslateController,
+                mKeyguardUnlockAnimationController);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 () -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index bb8bad3..f4f5bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -129,7 +129,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
-import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
@@ -226,7 +225,6 @@
     @Mock private NotificationMediaManager mNotificationMediaManager;
     @Mock private NavigationBarController mNavigationBarController;
     @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
-    @Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier;
     @Mock private SysuiColorExtractor mColorExtractor;
     @Mock private ColorExtractor.GradientColors mGradientColors;
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
@@ -394,7 +392,6 @@
                 mKeyguardStateController,
                 mHeadsUpManager,
                 mDynamicPrivacyController,
-                mBypassHeadsUpNotifier,
                 new FalsingManagerFake(),
                 new FalsingCollectorFake(),
                 mBroadcastDispatcher,
@@ -819,31 +816,27 @@
     }
 
     @Test
-    public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() {
-        mStatusBar.updateScrimController();
-        verify(mScrimController).setExpansionAffectsAlpha(eq(true));
-
-        clearInvocations(mScrimController);
-        when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true);
+    public void testSetExpansionAffectsAlpha_whenKeyguardShowingButGoingAwayForAnyReason() {
         mStatusBar.updateScrimController();
         verify(mScrimController).setExpansionAffectsAlpha(eq(true));
 
         clearInvocations(mScrimController);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
         mStatusBar.updateScrimController();
-        verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+        verify(mScrimController).setExpansionAffectsAlpha(eq(true));
 
         clearInvocations(mScrimController);
-        reset(mKeyguardStateController);
-        when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
-        mStatusBar.updateScrimController();
-        verify(mScrimController).setExpansionAffectsAlpha(eq(false));
-
-        clearInvocations(mScrimController);
-        reset(mKeyguardStateController);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
         mStatusBar.updateScrimController();
         verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+        clearInvocations(mScrimController);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+        mStatusBar.updateScrimController();
+        verify(mScrimController).setExpansionAffectsAlpha(eq(false));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 24a56bc..71b32c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.phone
 
 import android.animation.Animator
+import android.os.Handler
+import android.os.PowerManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
@@ -29,13 +31,19 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.GlobalSettings
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -53,7 +61,9 @@
     @Mock
     private lateinit var globalSettings: GlobalSettings
     @Mock
-    private lateinit var statusbar: StatusBar
+    private lateinit var statusBar: StatusBar
+    @Mock
+    private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
     @Mock
@@ -62,6 +72,10 @@
     private lateinit var statusBarStateController: StatusBarStateControllerImpl
     @Mock
     private lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock
+    private lateinit var powerManager: PowerManager
+    @Mock
+    private lateinit var handler: Handler
 
     @Before
     fun setUp() {
@@ -75,9 +89,24 @@
                 keyguardStateController,
                 dagger.Lazy<DozeParameters> { dozeParameters },
                 globalSettings,
-                interactionJankMonitor
+                interactionJankMonitor,
+                powerManager,
+                handler = handler
         )
-        controller.initialize(statusbar, lightRevealScrim)
+        controller.initialize(statusBar, lightRevealScrim)
+        `when`(statusBar.notificationPanelViewController).thenReturn(
+            notificationPanelViewController)
+
+        // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
+        // screen off.
+        `when`(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+    }
+
+    @After
+    fun cleanUp() {
+        // Tell the screen off controller to cancel the animations and clean up its state, or
+        // subsequent tests will act unpredictably as the animator continues running.
+        controller.onStartedWakingUp()
     }
 
     @Test
@@ -93,4 +122,49 @@
         listener.value.onAnimationEnd(null)
         Mockito.verify(animator).setListener(null)
     }
+
+    /**
+     * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
+     * animation to start. If the device is woken up during the screen off, we should *never* do
+     * this.
+     *
+     * This test confirms that we do show the AOD UI when the device is not woken up
+     * (PowerManager#isInteractive = false).
+     */
+    @Test
+    fun testAodUiShownIfNotInteractive() {
+        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+        `when`(powerManager.isInteractive).thenReturn(false)
+
+        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        controller.startAnimation()
+
+        verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+
+        callbackCaptor.value.run()
+
+        verify(notificationPanelViewController, times(1)).showAodUi()
+    }
+
+    /**
+     * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
+     * animation to start. If the device is woken up during the screen off, we should *never* do
+     * this.
+     *
+     * This test confirms that we do not show the AOD UI when the device is woken up during screen
+     * off (PowerManager#isInteractive = true).
+     */
+    @Test
+    fun testAodUiNotShownIfInteractive() {
+        `when`(dozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true)
+        `when`(powerManager.isInteractive).thenReturn(true)
+
+        val callbackCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        controller.startAnimation()
+
+        verify(handler).postDelayed(callbackCaptor.capture(), anyLong())
+        callbackCaptor.value.run()
+
+        verify(notificationPanelViewController, never()).showAodUi()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index d15ba26..d325840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -23,14 +23,20 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
+import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -40,12 +46,14 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -58,10 +66,15 @@
     private HeadsUpManager mHeadsUpManager;
     private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+    @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
+    @Mock private NotificationEntry mEntry;
+    @Mock private StatusBarNotification mSbn;
+    @Mock private Notification mNotification;
+    @Mock private HeadsUpManagerLogger mLogger;
 
     private final class TestableHeadsUpManager extends HeadsUpManager {
-        TestableHeadsUpManager(Context context) {
-            super(context, mock(HeadsUpManagerLogger.class));
+        TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger) {
+            super(context, logger);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
         }
@@ -73,10 +86,12 @@
 
     @Before
     public void setUp() {
+        initMocks(this);
         mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
         mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake);
-
-        mHeadsUpManager = new TestableHeadsUpManager(mContext);
+        when(mEntry.getSbn()).thenReturn(mSbn);
+        when(mSbn.getNotification()).thenReturn(mNotification);
+        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger);
         super.setUp();
         mHeadsUpManager.mHandler.removeCallbacksAndMessages(null);
         mHeadsUpManager.mHandler = mTestHandler;
@@ -88,6 +103,13 @@
     }
 
     @Test
+    public void testHunRemovedLogging() {
+        mAlertEntry.mEntry = mEntry;
+        mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
+        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey()));
+    }
+
+    @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
         doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
                 .getRecommendedTimeoutMillis(anyInt(), anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 8ccaf93..4a8170f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -33,7 +33,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,6 +41,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import dagger.Lazy;
+
 @SmallTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
@@ -52,9 +54,9 @@
     private LockPatternUtils mLockPatternUtils;
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private SmartspaceTransitionController mSmartSpaceTransitionController;
-    @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
 
     @Before
     public void setup() {
@@ -63,7 +65,7 @@
                 mContext,
                 mKeyguardUpdateMonitor,
                 mLockPatternUtils,
-                mSmartSpaceTransitionController,
+                mKeyguardUnlockAnimationControllerLazy,
                 mDumpManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index e136d00..aaea4ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -129,11 +129,6 @@
     }
 
     @Override
-    public boolean canPerformSmartSpaceTransition() {
-        return false;
-    }
-
-    @Override
     public boolean isKeyguardScreenRotationAllowed() {
         return false;
     }
diff --git a/services/Android.bp b/services/Android.bp
index f33c8c0..26760aa 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -179,6 +179,10 @@
     name: "libandroid_servers",
     defaults: ["libservices.core-libs"],
     whole_static_libs: ["libservices.core"],
+    required: [
+        // TODO: remove after NetworkStatsService is moved to the mainline module.
+        "libcom_android_net_module_util_jni",
+    ],
 }
 
 platform_compat_config {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 91e9093..5b580d9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3497,6 +3497,7 @@
             pw.append("hasWindowMagnificationConnection=").append(
                     String.valueOf(getWindowMagnificationMgr().isConnected()));
             pw.println();
+            mMagnificationProcessor.dump(pw, getValidDisplayList());
             final int userCount = mUserStates.size();
             for (int i = 0; i < userCount; i++) {
                 mUserStates.valueAt(i).dump(fd, pw, args);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0f35456..51b49ed 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -516,6 +516,8 @@
                 .append(String.valueOf(mMagnificationCapabilities));
         pw.append(", audioDescriptionByDefaultEnabled=")
                 .append(String.valueOf(mIsAudioDescriptionByDefaultRequested));
+        pw.append(", magnificationFollowTypingEnabled=")
+                .append(String.valueOf(mMagnificationFollowTypingEnabled));
         pw.append("}");
         pw.println();
         pw.append("     shortcut key:{");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index e39b979..fe97a46 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -769,6 +769,10 @@
         mMagnificationFollowTypingEnabled = enabled;
     }
 
+    boolean isMagnificationFollowTypingEnabled() {
+        return mMagnificationFollowTypingEnabled;
+    }
+
     /**
      * Remove the display magnification with given id.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index 8f15d5c..9eb77b4 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -26,6 +26,10 @@
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
 import android.graphics.Region;
+import android.view.Display;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Processor class for AccessibilityService connection to control magnification on the specified
@@ -360,4 +364,45 @@
     private void unregister(int displayId) {
         mController.getFullScreenMagnificationController().unregister(displayId);
     }
+
+    /**
+     * Dumps magnification configuration {@link MagnificationConfig} and state for each
+     * {@link Display}
+     */
+    public void dump(final PrintWriter pw, ArrayList<Display> displaysList) {
+        for (int i = 0; i < displaysList.size(); i++) {
+            final int displayId = displaysList.get(i).getDisplayId();
+
+            final MagnificationConfig config = getMagnificationConfig(displayId);
+            pw.println("Magnifier on display#" + displayId);
+            pw.append("    " + config).println();
+
+            final Region region = new Region();
+            getCurrentMagnificationRegion(displayId, region, true);
+            if (!region.isEmpty()) {
+                pw.append("    Magnification region=").append(region.toString()).println();
+            }
+            pw.append("    IdOfLastServiceToMagnify="
+                    + getIdOfLastServiceToMagnify(config.getMode(), displayId)).println();
+
+            dumpTrackingTypingFocusEnabledState(pw, displayId, config.getMode());
+        }
+    }
+
+    private int getIdOfLastServiceToMagnify(int mode, int displayId) {
+        return (mode == MAGNIFICATION_MODE_FULLSCREEN)
+                ? mController.getFullScreenMagnificationController()
+                .getIdOfLastServiceToMagnify(displayId)
+                : mController.getWindowMagnificationMgr().getIdOfLastServiceToMagnify(
+                        displayId);
+    }
+
+    private void dumpTrackingTypingFocusEnabledState(final PrintWriter pw, int displayId,
+            int mode) {
+        if (mode == MAGNIFICATION_MODE_WINDOW) {
+            pw.append("    TrackingTypingFocusEnabled="  + mController
+                            .getWindowMagnificationMgr().isTrackingTypingFocusEnabled(displayId))
+                    .println();
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 95c7d44..278f3f9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -318,6 +318,26 @@
         mMagnificationFollowTypingEnabled = enabled;
     }
 
+    boolean isMagnificationFollowTypingEnabled() {
+        return mMagnificationFollowTypingEnabled;
+    }
+
+    /**
+     * Get the ID of the last service that changed the magnification config.
+     *
+     * @param displayId The logical display id.
+     * @return The id
+     */
+    public int getIdOfLastServiceToMagnify(int displayId) {
+        synchronized (mLock) {
+            final WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+            if (magnifier != null) {
+                return magnifier.mIdOfLastServiceToControl;
+            }
+        }
+        return INVALID_SERVICE_ID;
+    }
+
     /**
      * Enable or disable tracking typing focus for the specific magnification window.
      *
@@ -466,6 +486,7 @@
         boolean previousEnabled;
         synchronized (mLock) {
             if (mConnectionWrapper == null) {
+                Slog.w(TAG, "enableWindowMagnification failed: connection null");
                 return false;
             }
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
@@ -508,6 +529,7 @@
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null || mConnectionWrapper == null) {
+                Slog.w(TAG, "disableWindowMagnification failed: connection " + mConnectionWrapper);
                 return false;
             }
             disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 0fd2967..75acf81 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -23,14 +23,18 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.compat.CompatChanges;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.view.Display;
 import android.window.DisplayWindowPolicyController;
 
 import java.util.List;
@@ -60,15 +64,29 @@
 
     @NonNull
     final ArraySet<Integer> mRunningUids = new ArraySet<>();
+    @Nullable private final ActivityListener mActivityListener;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    /**
+     * Creates a window policy controller that is generic to the different use cases of virtual
+     * device.
+     *
+     * @param windowFlags The window flags that this controller is interested in.
+     * @param systemWindowFlags The system window flags that this controller is interested in.
+     * @param allowedUsers The set of users that are allowed to stream in this display.
+     * @param activityListener Activity listener to listen for activity changes. The display ID
+     *   is not populated in this callback and is always {@link Display#INVALID_DISPLAY}.
+     */
     GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
             @Nullable Set<ComponentName> allowedActivities,
-            @Nullable Set<ComponentName> blockedActivities) {
+            @Nullable Set<ComponentName> blockedActivities,
+            @NonNull ActivityListener activityListener) {
         mAllowedUsers = allowedUsers;
         mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities);
         mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities);
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
+        mActivityListener = activityListener;
     }
 
     @Override
@@ -92,13 +110,21 @@
 
     @Override
     public void onTopActivityChanged(ComponentName topActivity, int uid) {
-
+        if (mActivityListener != null) {
+            // Post callback on the main thread so it doesn't block activity launching
+            mHandler.post(() ->
+                    mActivityListener.onTopActivityChanged(Display.INVALID_DISPLAY, topActivity));
+        }
     }
 
     @Override
     public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
         mRunningUids.clear();
         mRunningUids.addAll(runningUids);
+        if (mActivityListener != null && mRunningUids.isEmpty()) {
+            // Post callback on the main thread so it doesn't block activity launching
+            mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY));
+        }
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index ae39d7e..6c56e2f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -27,6 +27,7 @@
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
 import android.os.IBinder;
+import android.os.IInputConstants;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -126,6 +127,7 @@
             final InputManagerInternal inputManagerInternal =
                     LocalServices.getService(InputManagerInternal.class);
             inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+            inputManagerInternal.setPointerAcceleration(1);
             mActivePointerDisplayId = displayId;
         }
         try {
@@ -210,6 +212,8 @@
         final InputManagerInternal inputManagerInternal =
                 LocalServices.getService(InputManagerInternal.class);
         inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+        inputManagerInternal.setPointerAcceleration(
+                IInputConstants.DEFAULT_POINTER_ACCELERATION);
         mActivePointerDisplayId = Display.INVALID_DISPLAY;
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6ab8e75..5c8fb2e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,7 +29,10 @@
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -75,6 +78,30 @@
     private final OnDeviceCloseListener mListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
+    private final IVirtualDeviceActivityListener mActivityListener;
+
+    private ActivityListener createListenerAdapter(int displayId) {
+        return new ActivityListener() {
+
+            @Override
+            public void onTopActivityChanged(int unusedDisplayId, ComponentName topActivity) {
+                try {
+                    mActivityListener.onTopActivityChanged(displayId, topActivity);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                }
+            }
+
+            @Override
+            public void onDisplayEmpty(int unusedDisplayId) {
+                try {
+                    mActivityListener.onDisplayEmpty(displayId);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to call mActivityListener", e);
+                }
+            }
+        };
+    }
 
     /**
      * A mapping from the virtual display ID to its corresponding
@@ -85,18 +112,22 @@
 
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
             IBinder token, int ownerUid, OnDeviceCloseListener listener,
-            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback,
+            IVirtualDeviceActivityListener activityListener,
+            VirtualDeviceParams params) {
         this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener,
-                pendingTrampolineCallback, params);
+                pendingTrampolineCallback, activityListener, params);
     }
 
     @VisibleForTesting
     VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
             int ownerUid, InputController inputController, OnDeviceCloseListener listener,
-            PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) {
+            PendingTrampolineCallback pendingTrampolineCallback,
+            IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) {
         mContext = context;
         mAssociationInfo = associationInfo;
         mPendingTrampolineCallback = pendingTrampolineCallback;
+        mActivityListener = activityListener;
         mOwnerUid = ownerUid;
         mAppToken = token;
         mParams = params;
@@ -361,9 +392,11 @@
                 displayId, false);
         final GenericWindowPolicyController dwpc =
                 new GenericWindowPolicyController(FLAG_SECURE,
-                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(),
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        getAllowedUserHandles(),
                         mParams.getAllowedActivities(),
-                        mParams.getBlockedActivities());
+                        mParams.getBlockedActivities(),
+                        createListenerAdapter(displayId));
         mWindowPolicyControllers.put(displayId, dwpc);
         return dwpc;
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 7e0c2fc..b507110 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -27,6 +27,7 @@
 import android.companion.CompanionDeviceManager;
 import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
 import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
@@ -176,7 +177,8 @@
                 IBinder token,
                 String packageName,
                 int associationId,
-                @NonNull VirtualDeviceParams params) {
+                @NonNull VirtualDeviceParams params,
+                @NonNull IVirtualDeviceActivityListener activityListener) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                     "createVirtualDevice");
@@ -206,7 +208,7 @@
                                 }
                             }
                         },
-                        this, params);
+                        this, activityListener, params);
                 mVirtualDevices.put(associationInfo.getId(), virtualDevice);
                 return virtualDevice;
             }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 094ed37..1106fe7 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -176,6 +176,9 @@
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
         "com.android.sysprop.watchdog",
+        // This is used for services.connectivity-tiramisu-sources.
+        // TODO: delete when NetworkStatsService is moved to the mainline module.
+        "net-utils-device-common-bpf",
     ],
     javac_shard_size: 50,
 }
diff --git a/services/core/java/android/app/usage/OWNERS b/services/core/java/android/app/usage/OWNERS
new file mode 100644
index 0000000..3a55514
--- /dev/null
+++ b/services/core/java/android/app/usage/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/app/usage/OWNERS
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 21fc19e..435d294 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -17,6 +17,7 @@
 package android.app.usage;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -25,6 +26,7 @@
 import android.content.LocusId;
 import android.content.res.Configuration;
 import android.os.IBinder;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 
@@ -362,4 +364,52 @@
     /** Unregister a listener from being notified of every estimated launch time change. */
     public abstract void unregisterLaunchTimeChangedListener(
             @NonNull EstimatedLaunchTimeChangedListener listener);
+
+    /**
+     * Reports a broadcast dispatched event to the UsageStatsManager.
+     *
+     * @param sourceUid uid of the package that sent the broadcast.
+     * @param targetPackage name of the package that the broadcast is targeted to.
+     * @param targetUser user that {@code targetPackage} belongs to.
+     * @param idForResponseEvent ID to be used for recording any response events corresponding
+     *                           to this broadcast.
+     * @param timestampMs time (in millis) when the broadcast was dispatched, in
+     *                    {@link SystemClock#elapsedRealtime()} timebase.
+     */
+    public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
+            @NonNull UserHandle targetUser, long idForResponseEvent,
+            @ElapsedRealtimeLong long timestampMs);
+
+    /**
+     * Reports a notification posted event to the UsageStatsManager.
+     *
+     * @param packageName name of the package which posted the notification.
+     * @param user user that {@code packageName} belongs to.
+     * @param timestampMs time (in millis) when the notification was posted, in
+     *                    {@link SystemClock#elapsedRealtime()} timebase.
+     */
+    public abstract void reportNotificationPosted(@NonNull String packageName,
+            @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
+
+    /**
+     * Reports a notification updated event to the UsageStatsManager.
+     *
+     * @param packageName name of the package which updated the notification.
+     * @param user user that {@code packageName} belongs to.
+     * @param timestampMs time (in millis) when the notification was updated, in
+     *                    {@link SystemClock#elapsedRealtime()} timebase.
+     */
+    public abstract void reportNotificationUpdated(@NonNull String packageName,
+            @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
+
+    /**
+     * Reports a notification removed event to the UsageStatsManager.
+     *
+     * @param packageName name of the package which removed the notification.
+     * @param user user that {@code packageName} belongs to.
+     * @param timestampMs time (in millis) when the notification was removed, in
+     *                    {@link SystemClock#elapsedRealtime()} timebase.
+     */
+    public abstract void reportNotificationRemoved(@NonNull String packageName,
+            @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs);
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 3951680..8551d88 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,12 +20,12 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
 import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_ALLOWLIST;
-import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_NONE;
-import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
@@ -44,6 +44,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
@@ -1158,19 +1159,12 @@
             }
 
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "inetd bandwidth");
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
                 if (allowlist) {
-                    if (enable) {
-                        mNetdService.bandwidthAddNiceApp(uid);
-                    } else {
-                        mNetdService.bandwidthRemoveNiceApp(uid);
-                    }
+                    cm.updateMeteredNetworkAllowList(uid, enable);
                 } else {
-                    if (enable) {
-                        mNetdService.bandwidthAddNaughtyApp(uid);
-                    } else {
-                        mNetdService.bandwidthRemoveNaughtyApp(uid);
-                    }
+                    cm.updateMeteredNetworkDenyList(uid, enable);
                 }
                 synchronized (mRulesLock) {
                     if (enable) {
@@ -1179,7 +1173,7 @@
                         quotaList.delete(uid);
                     }
                 }
-            } catch (RemoteException | ServiceSpecificException e) {
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -1464,9 +1458,10 @@
                 throw new IllegalArgumentException("Bad child chain: " + chainName);
             }
 
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                mNetdService.firewallEnableChildChain(chain, enable);
-            } catch (RemoteException | ServiceSpecificException e) {
+                cm.setFirewallChainEnabled(chain, enable);
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             }
 
@@ -1538,25 +1533,10 @@
                     updateFirewallUidRuleLocked(chain, uid, FIREWALL_RULE_DEFAULT);
                 }
             }
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                switch (chain) {
-                    case FIREWALL_CHAIN_DOZABLE:
-                        mNetdService.firewallReplaceUidChain("fw_dozable", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_STANDBY:
-                        mNetdService.firewallReplaceUidChain("fw_standby", false, uids);
-                        break;
-                    case FIREWALL_CHAIN_POWERSAVE:
-                        mNetdService.firewallReplaceUidChain("fw_powersave", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_RESTRICTED:
-                        mNetdService.firewallReplaceUidChain("fw_restricted", true, uids);
-                        break;
-                    case FIREWALL_CHAIN_NONE:
-                    default:
-                        Slog.d(TAG, "setFirewallUidRules() called on invalid chain: " + chain);
-                }
-            } catch (RemoteException e) {
+                cm.replaceFirewallChain(chain, uids);
+            } catch (RuntimeException e) {
                 Slog.w(TAG, "Error flushing firewall chain " + chain, e);
             }
         }
@@ -1572,10 +1552,10 @@
 
     private void setFirewallUidRuleLocked(int chain, int uid, int rule) {
         if (updateFirewallUidRuleLocked(chain, uid, rule)) {
-            final int ruleType = getFirewallRuleType(chain, rule);
+            final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             try {
-                mNetdService.firewallSetUidRule(chain, uid, ruleType);
-            } catch (RemoteException | ServiceSpecificException e) {
+                cm.updateFirewallRule(chain, uid, isFirewallRuleAllow(chain, rule));
+            } catch (RuntimeException e) {
                 throw new IllegalStateException(e);
             }
         }
@@ -1645,12 +1625,12 @@
         }
     }
 
-    private int getFirewallRuleType(int chain, int rule) {
+    // There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY.
+    private boolean isFirewallRuleAllow(int chain, int rule) {
         if (rule == NetworkPolicyManager.FIREWALL_RULE_DEFAULT) {
-            return getFirewallType(chain) == FIREWALL_ALLOWLIST
-                    ? INetd.FIREWALL_RULE_DENY : INetd.FIREWALL_RULE_ALLOW;
+            return getFirewallType(chain) == FIREWALL_DENYLIST;
         }
-        return rule;
+        return rule == INetd.FIREWALL_RULE_ALLOW;
     }
 
     private void enforceSystemUid() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c062365..b6a0ec4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -98,6 +98,7 @@
 import android.util.DisplayMetrics;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.window.SplashScreen;
 
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.util.HexDump;
@@ -178,6 +179,7 @@
     private boolean mIsLockTask;
     private boolean mAsync;
     private BroadcastOptions mBroadcastOptions;
+    private boolean mShowSplashScreen;
 
     final boolean mDumping;
 
@@ -428,6 +430,8 @@
                     mBroadcastOptions.setBackgroundActivityStartsAllowed(true);
                 } else if (opt.equals("--async")) {
                     mAsync = true;
+                } else if (opt.equals("--splashscreen-show-icon")) {
+                    mShowSplashScreen = true;
                 } else {
                     return false;
                 }
@@ -566,6 +570,12 @@
                 }
                 options.setLockTaskEnabled(true);
             }
+            if (mShowSplashScreen) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+            }
             if (mWaitOption) {
                 result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
                         mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -3301,6 +3311,7 @@
             pw.println("      --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
             pw.println("      --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
             pw.println("      --display <DISPLAY_ID>: The display to launch the activity into.");
+            pw.println("      --splashscreen-icon: Show the splash screen icon on launch.");
             pw.println("  start-service [--user <USER_ID> | current] <INTENT>");
             pw.println("      Start a Service.  Options are:");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e36ea20..bb939b7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -67,6 +67,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.FileDescriptor;
@@ -1832,7 +1833,7 @@
 
     private boolean noteOpForManifestReceiver(int appOp, BroadcastRecord r, ResolveInfo info,
             ComponentName component) {
-        if (info.activityInfo.attributionTags == null) {
+        if (ArrayUtils.isEmpty(info.activityInfo.attributionTags)) {
             return noteOpForManifestReceiverInner(appOp, r, info, component, null);
         } else {
             // Attribution tags provided, noteOp each tag
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3c5b872..df792ee2 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1046,6 +1046,16 @@
             userId = UserHandle.getCallingUserId();
         }
 
+        if (isAuthorityRedirectedForCloneProfile(authority)) {
+            UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+            UserInfo userInfo = umInternal.getUserInfo(userId);
+
+            if (userInfo != null && userInfo.isCloneProfile()) {
+                userId = umInternal.getProfileParentId(userId);
+                checkUser = false;
+            }
+        }
+
         ProviderInfo cpi = null;
         try {
             cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
@@ -1055,17 +1065,6 @@
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                     userId);
-            if (cpi == null && isAuthorityRedirectedForCloneProfile(authority)) {
-                // This might be a provider that's running only in the system user that's
-                // also serving clone profiles
-                cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
-                        ActivityManagerService.STOCK_PM_FLAGS
-                                | PackageManager.GET_URI_PERMISSION_PATTERNS
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                        UserHandle.USER_SYSTEM);
-            }
         } catch (RemoteException ignored) {
         }
         if (cpi == null) {
@@ -1073,16 +1072,6 @@
                     + "; expected to find a valid ContentProvider for this authority";
         }
 
-        if (isAuthorityRedirectedForCloneProfile(authority)) {
-            UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-            UserInfo userInfo = umInternal.getUserInfo(userId);
-
-            if (userInfo != null && userInfo.isCloneProfile()) {
-                userId = umInternal.getProfileParentId(userId);
-                checkUser = false;
-            }
-        }
-
         final int callingPid = Binder.getCallingPid();
         ProcessRecord r;
         final String appName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index f5f7bb3..417ebcd 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -938,6 +938,19 @@
         }
     }
 
+    /**
+     * Remove frame rate override due to mode switch
+     */
+    private void resetFps(String packageName, @UserIdInt int userId) {
+        try {
+            final float fps = 0.0f;
+            final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
+            nativeSetOverrideFrameRate(uid, fps);
+        } catch (PackageManager.NameNotFoundException e) {
+            return;
+        }
+    }
+
     private void enableCompatScale(String packageName, long scaleId) {
         final long uid = Binder.clearCallingIdentity();
         try {
@@ -1030,6 +1043,7 @@
         if (gameMode == GameManager.GAME_MODE_STANDARD
                 || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
             disableCompatScale(packageName);
+            resetFps(packageName, userId);
             return;
         }
         GamePackageConfiguration packageConfig = null;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 565e295..65be5f0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -59,6 +59,7 @@
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
@@ -1794,4 +1795,10 @@
         }
         return null;
     }
+
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.getDeviceSensorUuid(device);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 2dd6bf5..f5a529b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -48,7 +48,9 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Class to manage the inventory of all connected devices.
@@ -185,12 +187,20 @@
         final @NonNull String mDeviceName;
         final @NonNull String mDeviceAddress;
         int mDeviceCodecFormat;
+        final UUID mSensorUuid;
 
-        DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+                   int deviceCodecFormat, UUID sensorUuid) {
             mDeviceType = deviceType;
             mDeviceName = deviceName == null ? "" : deviceName;
             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
             mDeviceCodecFormat = deviceCodecFormat;
+            mSensorUuid = sensorUuid;
+        }
+
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress,
+                   int deviceCodecFormat) {
+            this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
         }
 
         @Override
@@ -199,7 +209,8 @@
                     + " (" + AudioSystem.getDeviceName(mDeviceType)
                     + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
-                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+                    + " codec: " + Integer.toHexString(mDeviceCodecFormat)
+                    + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
         }
 
         @NonNull String getKey() {
@@ -980,8 +991,13 @@
         // Reset A2DP suspend state each time a new sink is connected
         mAudioSystem.setParameters("A2dpSuspended=false");
 
+        // The convention for head tracking sensors associated with A2DP devices is to
+        // use a UUID derived from the MAC address as follows:
+        //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
+        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
+                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                address, a2dpCodec);
+                address, a2dpCodec, sensorUuid);
         final String diKey = di.getKey();
         mConnectedDevices.put(diKey, di);
         // on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1456,6 +1472,17 @@
         mDevRoleCapturePresetDispatchers.finishBroadcast();
     }
 
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+                device.getAddress());
+        synchronized (mDevicesLock) {
+            DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                return null;
+            }
+            return di.mSensorUuid;
+        }
+    }
     //----------------------------------------------------------
     // For tests only
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0ea936e..a853ac5 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -190,6 +190,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -11017,6 +11018,10 @@
         return delayMillis;
     }
 
+    UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+        return mDeviceBroker.getDeviceSensorUuid(device);
+    }
+
     //======================
     // misc
     //======================
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e6789d5..106cbba 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -43,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.UUID;
 
 /**
  * A helper class to manage Spatializer related functionality
@@ -168,6 +169,7 @@
             return;
         }
         mState = STATE_DISABLED_UNAVAILABLE;
+        mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
         // note at this point mSpat is still not instantiated
     }
 
@@ -204,6 +206,11 @@
         logd("onRoutingUpdated: can spatialize media 5.1:" + able
                 + " on device:" + ROUTING_DEVICES[0]);
         setDispatchAvailableState(able);
+
+        if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED
+                && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
+            postInitSensors();
+        }
     }
 
     //------------------------------------------------------
@@ -909,12 +916,21 @@
                 }
             }
             // initialize sensor handles
-            // TODO-HT update to non-private sensor once head tracker sensor is defined
-            for (Sensor sensor : mSensorManager.getDynamicSensorList(
-                    Sensor.TYPE_DEVICE_PRIVATE_BASE)) {
-                if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
-                    headHandle = sensor.getHandle();
-                    break;
+            UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+            List<Sensor> sensors = new ArrayList<Sensor>(0);
+            sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER));
+            sensors.addAll(mSensorManager.getDynamicSensorList(Sensor.TYPE_DEVICE_PRIVATE_BASE));
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == Sensor.TYPE_HEAD_TRACKER
+                        || sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
+                    UUID uuid = sensor.getUuid();
+                    if (uuid.equals(routingDeviceUuid)) {
+                        headHandle = sensor.getHandle();
+                        break;
+                    }
+                    if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+                        headHandle = sensor.getHandle();
+                    }
                 }
             }
             Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java
new file mode 100644
index 0000000..2003619
--- /dev/null
+++ b/services/core/java/com/android/server/audio/UuidUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import android.media.AudioDeviceAttributes;
+import android.media.AudioSystem;
+import android.util.Slog;
+
+import java.util.UUID;
+
+/**
+ *  UuidUtils class implements helper functions to handle unique identifiers
+ *  used to associate head tracking sensors to audio devices.
+ */
+class UuidUtils {
+    private static final String TAG = "AudioService.UuidUtils";
+
+    private static final long LSB_PREFIX_MASK = 0xFFFF000000000000L;
+    private static final long LSB_SUFFIX_MASK = 0x0000FFFFFFFFFFFFL;
+    // The sensor UUID for Bluetooth devices is defined as follows:
+    // - 8 most significant bytes: All 0s
+    // - 8 most significant bytes: Ascii B, Ascii T, Device MAC address on 6 bytes
+    private static final long LSB_PREFIX_BT = 0x4254000000000000L;
+
+    /**
+     * Special UUID for a head tracking sensor not associated with an audio device.
+     */
+    public static final UUID STANDALONE_UUID = new UUID(0, 0);
+
+    /**
+     *  Generate a headtracking UUID from AudioDeviceAttributes
+     */
+    public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) {
+        switch (device.getInternalType()) {
+            case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
+                String address = device.getAddress().replace(":", "");
+                if (address.length() != 12) {
+                    return null;
+                }
+                address = "0x" + address;
+                long lsb = LSB_PREFIX_BT;
+                try {
+                    lsb |= Long.decode(address).longValue();
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+                if (AudioService.DEBUG_DEVICES) {
+                    Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
+                }
+                return new UUID(0, lsb);
+            default:
+                // Handle other device types here
+                return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index fce6737..a5024ff 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -18,7 +18,6 @@
 
 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
 import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
-import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
@@ -409,8 +408,8 @@
 
         private void registerUsageCallback(long budget) {
             maybeUnregisterUsageCallback();
-            mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
-                    mUsageCallback, mHandler);
+            mStatsManager.registerUsageCallback(mNetworkTemplate, budget,
+                    (command) -> mHandler.post(command), mUsageCallback);
             mMultipathBudget = budget;
         }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 709af91..c2ca3a5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -51,7 +51,6 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.policy.DeviceStatePolicyImpl;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowProcessController;
 
@@ -142,7 +141,9 @@
     private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
 
     public DeviceStateManagerService(@NonNull Context context) {
-        this(context, new DeviceStatePolicyImpl(context));
+        this(context, DeviceStatePolicy.Provider
+                .fromResources(context.getResources())
+                .instantiate(context));
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
index 274b8e5..5c4e2f3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -17,6 +17,11 @@
 package com.android.server.devicestate;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.server.policy.DeviceStatePolicyImpl;
 
 /**
  * Interface for the component responsible for supplying the current device state as well as
@@ -24,9 +29,15 @@
  *
  * @see DeviceStateManagerService
  */
-public interface DeviceStatePolicy {
+public abstract class DeviceStatePolicy {
+    protected final Context mContext;
+
+    protected DeviceStatePolicy(@NonNull Context context) {
+        mContext = context;
+    }
+
     /** Returns the provider of device states. */
-    DeviceStateProvider getDeviceStateProvider();
+    public abstract DeviceStateProvider getDeviceStateProvider();
 
     /**
      * Configures the system into the provided state. Guaranteed not to be called again until the
@@ -36,5 +47,58 @@
      * @param onComplete a callback that must be triggered once the system has been properly
      *                   configured to match the supplied state.
      */
-    void configureDeviceForState(int state, @NonNull Runnable onComplete);
+    public abstract void configureDeviceForState(int state, @NonNull Runnable onComplete);
+
+    /** Provider for platform-default device state policy. */
+    static final class DefaultProvider implements DeviceStatePolicy.Provider {
+        @Override
+        public DeviceStatePolicy instantiate(@NonNull Context context) {
+            return new DeviceStatePolicyImpl(context);
+        }
+    }
+
+    /**
+     * Provider for {@link DeviceStatePolicy} instances.
+     *
+     * <p>By implementing this interface and overriding the
+     * {@code config_deviceSpecificDeviceStatePolicyProvider}, a device-specific implementations
+     * of {@link DeviceStatePolicy} can be supplied.
+     */
+    public interface Provider {
+        /**
+         * Instantiates a new {@link DeviceStatePolicy}.
+         *
+         * @see DeviceStatePolicy#DeviceStatePolicy
+         */
+        DeviceStatePolicy instantiate(@NonNull Context context);
+
+        /**
+         * Instantiates the device-specific {@link DeviceStatePolicy.Provider}.
+         *
+         * Checks the {@code config_deviceSpecificDeviceStatePolicyProvider} resource to see if
+         * a device specific policy provider has been supplied. If so, returns an instance of that
+         * provider. If there is no value provided then the method returns the
+         * {@link DeviceStatePolicy.DefaultProvider}.
+         *
+         * An {@link IllegalStateException} is thrown if there is a value provided for that
+         * resource, but it doesn't correspond to a class that is found.
+         */
+        static Provider fromResources(@NonNull Resources res) {
+            final String name = res.getString(
+                    com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider);
+            if (TextUtils.isEmpty(name)) {
+                return new DeviceStatePolicy.DefaultProvider();
+            }
+
+            try {
+                return (DeviceStatePolicy.Provider) Class.forName(name).newInstance();
+            } catch (ReflectiveOperationException | ClassCastException e) {
+                throw new IllegalStateException("Couldn't instantiate class " + name
+                        + " for config_deviceSpecificDeviceStatePolicyProvider:"
+                        + " make sure it has a public zero-argument constructor"
+                        + " and implements DeviceStatePolicy.Provider", e);
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 3df2422..f3969b1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.R;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.config.BrightnessThresholds;
 import com.android.server.display.config.Density;
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
@@ -42,6 +43,7 @@
 import com.android.server.display.config.RefreshRateRange;
 import com.android.server.display.config.SensorDetails;
 import com.android.server.display.config.ThermalStatus;
+import com.android.server.display.config.Thresholds;
 import com.android.server.display.config.XmlParser;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -130,6 +132,10 @@
     private float mBrightnessRampSlowIncrease = Float.NaN;
     private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
     private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
+    private float mScreenBrighteningMinThreshold = 0.0f;     // Retain behaviour as though there is
+    private float mScreenDarkeningMinThreshold = 0.0f;       // no minimum threshold for change in
+    private float mAmbientLuxBrighteningMinThreshold = 0.0f; // screen brightness or ambient
+    private float mAmbientLuxDarkeningMinThreshold = 0.0f;   // brightness.
     private Spline mBrightnessToBacklightSpline;
     private Spline mBacklightToBrightnessSpline;
     private Spline mBacklightToNitsSpline;
@@ -364,6 +370,22 @@
         return mAmbientHorizonShort;
     }
 
+    public float getScreenBrighteningMinThreshold() {
+        return mScreenBrighteningMinThreshold;
+    }
+
+    public float getScreenDarkeningMinThreshold() {
+        return mScreenDarkeningMinThreshold;
+    }
+
+    public float getAmbientLuxBrighteningMinThreshold() {
+        return mAmbientLuxBrighteningMinThreshold;
+    }
+
+    public float getAmbientLuxDarkeningMinThreshold() {
+        return mAmbientLuxDarkeningMinThreshold;
+    }
+
     SensorData getAmbientLightSensor() {
         return mAmbientLightSensor;
     }
@@ -425,6 +447,10 @@
                 + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
                 + ", mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
+                + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+                + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
+                + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
+                + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
                 + ", mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -482,6 +508,7 @@
                 loadAmbientLightSensorFromDdc(config);
                 loadProxSensorFromDdc(config);
                 loadAmbientHorizonFromDdc(config);
+                loadBrightnessChangeThresholds(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
@@ -865,6 +892,45 @@
         }
     }
 
+    private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
+        Thresholds displayBrightnessThresholds = config.getDisplayBrightnessChangeThresholds();
+        Thresholds ambientBrightnessThresholds = config.getAmbientBrightnessChangeThresholds();
+
+        if (displayBrightnessThresholds != null) {
+            BrightnessThresholds brighteningScreen =
+                    displayBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningScreen =
+                    displayBrightnessThresholds.getDarkeningThresholds();
+
+            final BigDecimal screenBrighteningThreshold = brighteningScreen.getMinimum();
+            final BigDecimal screenDarkeningThreshold = darkeningScreen.getMinimum();
+
+            if (screenBrighteningThreshold != null) {
+                mScreenBrighteningMinThreshold = screenBrighteningThreshold.floatValue();
+            }
+            if (screenDarkeningThreshold != null) {
+                mScreenDarkeningMinThreshold = screenDarkeningThreshold.floatValue();
+            }
+        }
+
+        if (ambientBrightnessThresholds != null) {
+            BrightnessThresholds brighteningAmbientLux =
+                    ambientBrightnessThresholds.getBrighteningThresholds();
+            BrightnessThresholds darkeningAmbientLux =
+                    ambientBrightnessThresholds.getDarkeningThresholds();
+
+            final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum();
+            final BigDecimal ambientDarkeningThreshold =  darkeningAmbientLux.getMinimum();
+
+            if (ambientBrighteningThreshold != null) {
+                mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue();
+            }
+            if (ambientDarkeningThreshold != null) {
+                mAmbientLuxDarkeningMinThreshold = ambientDarkeningThreshold.floatValue();
+            }
+        }
+    }
+
     private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) {
         if (value == null) {
             return PowerManager.THERMAL_STATUS_NONE;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c6d3829..31c496ed 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -909,9 +909,14 @@
                     com.android.internal.R.array.config_ambientDarkeningThresholds);
             int[] ambientThresholdLevels = resources.getIntArray(
                     com.android.internal.R.array.config_ambientThresholdLevels);
+            float ambientDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+            float ambientBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
             HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
                     ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientThresholdLevels);
+                    ambientThresholdLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningMinThreshold);
 
             int[] screenBrighteningThresholds = resources.getIntArray(
                     com.android.internal.R.array.config_screenBrighteningThresholds);
@@ -919,8 +924,13 @@
                     com.android.internal.R.array.config_screenDarkeningThresholds);
             int[] screenThresholdLevels = resources.getIntArray(
                     com.android.internal.R.array.config_screenThresholdLevels);
+            float screenDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+            float screenBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
             HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels);
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+                    screenDarkeningMinThreshold, screenBrighteningMinThreshold);
 
             long brighteningLightDebounce = resources.getInteger(
                     com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 2b56569..7a932ce 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -30,17 +30,13 @@
 public class HysteresisLevels {
     private static final String TAG = "HysteresisLevels";
 
-    // Default hysteresis constraints for brightening or darkening.
-    // The recent value must have changed by at least this fraction relative to the
-    // current value before a change will be considered.
-    private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
-    private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
-
     private static final boolean DEBUG = false;
 
     private final float[] mBrighteningThresholds;
     private final float[] mDarkeningThresholds;
     private final float[] mThresholdLevels;
+    private final float mMinDarkening;
+    private final float mMinBrightening;
 
     /**
      * Creates a {@code HysteresisLevels} object with the given equal-length
@@ -48,9 +44,12 @@
      * @param brighteningThresholds an array of brightening hysteresis constraint constants.
      * @param darkeningThresholds an array of darkening hysteresis constraint constants.
      * @param thresholdLevels a monotonically increasing array of threshold levels.
+     * @param minBrighteningThreshold the minimum value for which the brightening value needs to
+     *                                return.
+     * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
     */
     HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
-            int[] thresholdLevels) {
+            int[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
         if (brighteningThresholds.length != darkeningThresholds.length
                 || darkeningThresholds.length != thresholdLevels.length + 1) {
             throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
@@ -58,6 +57,8 @@
         mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
         mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
         mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
+        mMinDarkening = minDarkeningThreshold;
+        mMinBrightening = minBrighteningThreshold;
     }
 
     /**
@@ -65,11 +66,13 @@
      */
     public float getBrighteningThreshold(float value) {
         final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
-        final float brightThreshold = value * (1.0f + brightConstant);
+        float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
             Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
                     + brightThreshold + ", value=" + value);
         }
+
+        brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
         return brightThreshold;
     }
 
@@ -78,12 +81,13 @@
      */
     public float getDarkeningThreshold(float value) {
         final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
-        final float darkThreshold = value * (1.0f - darkConstant);
+        float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
                     + darkThreshold + ", value=" + value);
         }
-        return darkThreshold;
+        darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+        return Math.max(darkThreshold, 0.0f);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4de39bc..b9c7123 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.input;
 
+import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -289,6 +291,8 @@
             int deviceId, int sourceMask, int sw);
     private static native boolean nativeHasKeys(long ptr,
             int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+    private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId,
+            int locationKeyCode);
     private static native InputChannel nativeCreateInputChannel(long ptr, String name);
     private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
             boolean isGestureMonitor, String name, int pid);
@@ -313,6 +317,7 @@
             IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
     private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
+    private static native void nativeSetPointerAcceleration(long ptr, float acceleration);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
     private static native void nativeReloadCalibration(long ptr);
@@ -666,6 +671,22 @@
     }
 
     /**
+     * Returns the keyCode generated by the specified location on a US keyboard layout.
+     * This takes into consideration the currently active keyboard layout.
+     *
+     * @param deviceId The input device id.
+     * @param locationKeyCode The location of a key on a US keyboard layout.
+     * @return The KeyCode this physical key location produces.
+     */
+    @Override // Binder call
+    public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
+        if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) {
+            return KEYCODE_UNKNOWN;
+        }
+        return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode);
+    }
+
+    /**
      * Transfer the current touch gesture to the provided window.
      *
      * @param destChannelToken The token of the window or input channel that should receive the
@@ -1777,6 +1798,10 @@
         nativeSetPointerSpeed(mPtr, speed);
     }
 
+    private void setPointerAcceleration(float acceleration) {
+        nativeSetPointerAcceleration(mPtr, acceleration);
+    }
+
     private void registerPointerSpeedSettingObserver() {
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
@@ -3467,6 +3492,11 @@
         }
 
         @Override
+        public void setPointerAcceleration(float acceleration) {
+            InputManagerService.this.setPointerAcceleration(acceleration);
+        }
+
+        @Override
         public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
             InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible);
         }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
new file mode 100644
index 0000000..c86ebd2
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+
+import java.util.List;
+
+/**
+ * A wrapper class to invoke IPCs defined in {@link IInputMethod}.
+ */
+final class IInputMethodInvoker {
+    private static final String TAG = InputMethodManagerService.TAG;
+    private static final boolean DEBUG = InputMethodManagerService.DEBUG;
+
+    @AnyThread
+    @Nullable
+    static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) {
+        if (inputMethod == null) {
+            return null;
+        }
+        if (!Binder.isProxy(inputMethod)) {
+            // IInputMethodInvoker must be used only within the system_server and InputMethodService
+            // must not be running in the system_server.  Therefore, "inputMethod" must be a Proxy.
+            throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy.");
+        }
+        return new IInputMethodInvoker(inputMethod);
+    }
+
+    /**
+     * A simplified version of {@link android.os.Debug#getCaller()}.
+     *
+     * @return method name of the caller.
+     */
+    @AnyThread
+    private static String getCallerMethodName() {
+        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+        if (callStack.length <= 4) {
+            return "<bottom of call stack>";
+        }
+        return callStack[4].getMethodName();
+    }
+
+    @AnyThread
+    private static void logRemoteException(@NonNull RemoteException e) {
+        if (DEBUG || !(e instanceof DeadObjectException)) {
+            Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e);
+        }
+    }
+
+    @AnyThread
+    static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) {
+        if (obj == null) {
+            return 0;
+        }
+
+        return System.identityHashCode(obj.mTarget);
+    }
+
+    @NonNull
+    private final IInputMethod mTarget;
+
+    private IInputMethodInvoker(@NonNull IInputMethod target) {
+        mTarget = target;
+    }
+
+    @AnyThread
+    @NonNull
+    IBinder asBinder() {
+        return mTarget.asBinder();
+    }
+
+    @AnyThread
+    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
+            int configChanges, boolean stylusHwSupported) {
+        try {
+            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
+            IInlineSuggestionsRequestCallback cb) {
+        try {
+            mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void bindInput(InputBinding binding) {
+        try {
+            mTarget.bindInput(binding);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void unbindInput() {
+        try {
+            mTarget.unbindInput();
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
+            boolean restarting) {
+        try {
+            mTarget.startInput(startInputToken, inputContext, attribute, restarting);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void createSession(InputChannel channel, IInputSessionCallback callback) {
+        try {
+            mTarget.createSession(channel, callback);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void setSessionEnabled(IInputMethodSession session, boolean enabled) {
+        try {
+            mTarget.setSessionEnabled(session, enabled);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    // TODO(b/192412909): Convert this back to void method
+    @AnyThread
+    boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
+        try {
+            mTarget.showSoftInput(showInputToken, flags, resultReceiver);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+            return false;
+        }
+        return true;
+    }
+
+    // TODO(b/192412909): Convert this back to void method
+    @AnyThread
+    boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
+        try {
+            mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+            return false;
+        }
+        return true;
+    }
+
+    @AnyThread
+    void changeInputMethodSubtype(InputMethodSubtype subtype) {
+        try {
+            mTarget.changeInputMethodSubtype(subtype);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void canStartStylusHandwriting(int requestId) {
+        try {
+            mTarget.canStartStylusHandwriting(requestId);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+
+    @AnyThread
+    void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) {
+        try {
+            mTarget.startStylusHandwriting(channel, events);
+        } catch (RemoteException e) {
+            logRemoteException(e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 784d058..2230dcd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -75,7 +75,7 @@
     @GuardedBy("ImfLock.class") @Nullable private String mCurId;
     @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
     @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
-    @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod;
+    @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
     @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
     @GuardedBy("ImfLock.class") private IBinder mCurToken;
     @GuardedBy("ImfLock.class") private int mCurSeq;
@@ -241,7 +241,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
-    IInputMethod getCurMethod() {
+    IInputMethodInvoker getCurMethod() {
         return mCurMethod;
     }
 
@@ -298,7 +298,7 @@
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
             synchronized (ImfLock.class) {
                 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
-                    mCurMethod = IInputMethod.Stub.asInterface(service);
+                    mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
                     updateCurrentMethodUid();
                     if (mCurToken == null) {
                         Slog.w(TAG, "Service connected without a token!");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java
new file mode 100644
index 0000000..1779333
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDialogWindowContext.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides the window context for the IME switcher dialog.
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class InputMethodDialogWindowContext {
+    @Nullable
+    private Context mDialogWindowContext;
+
+    /**
+     * Returns the window context for IME switch dialogs to receive configuration changes.
+     *
+     * This method initializes the window context if it was not initialized, or moves the context to
+     * the targeted display if the current display of context is different from the display
+     * specified by {@code displayId}.
+     */
+    @NonNull
+    @VisibleForTesting(visibility = PACKAGE)
+    public Context get(int displayId) {
+        if (mDialogWindowContext == null || mDialogWindowContext.getDisplayId() != displayId) {
+            final Context systemUiContext = ActivityThread.currentActivityThread()
+                    .getSystemUiContext(displayId);
+            final Context windowContext = systemUiContext.createWindowContext(
+                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
+            mDialogWindowContext = new ContextThemeWrapper(
+                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
+        }
+        return mDialogWindowContext;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2a24489..8a6aa0d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -160,14 +160,12 @@
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
 import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
 import com.android.internal.view.IInputMethodSession;
@@ -271,7 +269,6 @@
     final WindowManagerInternal mWindowManagerInternal;
     final PackageManagerInternal mPackageManagerInternal;
     final InputManagerInternal mInputManagerInternal;
-    private final HandlerCaller mCaller;
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
@@ -329,7 +326,7 @@
 
     static class SessionState {
         final ClientState client;
-        final IInputMethod method;
+        final IInputMethodInvoker method;
 
         IInputMethodSession session;
         InputChannel channel;
@@ -338,14 +335,14 @@
         public String toString() {
             return "SessionState{uid " + client.uid + " pid " + client.pid
                     + " method " + Integer.toHexString(
-                            System.identityHashCode(method))
+                            IInputMethodInvoker.getBinderIdentityHashCode(method))
                     + " session " + Integer.toHexString(
                             System.identityHashCode(session))
                     + " channel " + channel
                     + "}";
         }
 
-        SessionState(ClientState _client, IInputMethod _method,
+        SessionState(ClientState _client, IInputMethodInvoker _method,
                 IInputMethodSession _session, InputChannel _channel) {
             client = _client;
             method = _method;
@@ -613,7 +610,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
-    private IInputMethod getCurMethodLocked() {
+    private IInputMethodInvoker getCurMethodLocked() {
         return mBindingController.getCurMethod();
     }
 
@@ -692,8 +689,8 @@
 
     /**
      * Internal state snapshot when
-     * {@link IInputMethod#startInput(IBinder, IInputContext, EditorInfo, boolean)} is about to be
-     * called.
+     * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo,
+     * boolean)} is about to be called.
      *
      * <p>Calling that IPC endpoint basically means that
      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1527,8 +1524,8 @@
         @Override
         public void onUserUnlocking(@NonNull TargetUser user) {
             // Called on ActivityManager thread.
-            mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
-                    /* arg1= */ user.getUserIdentifier(), /* arg2= */ 0));
+            mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
+                    .sendToTarget();
         }
     }
 
@@ -1588,8 +1585,6 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
-        mCaller = new HandlerCaller(context, thread.getLooper(), this::handleMessage,
-                true /*asyncHandler*/);
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1951,18 +1946,14 @@
             InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
         final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
         try {
-            IInputMethod curMethod = getCurMethodLocked();
+            IInputMethodInvoker curMethod = getCurMethodLocked();
             if (userId == mSettings.getCurrentUserId() && imi != null
                     && imi.isInlineSuggestionsEnabled() && curMethod != null) {
                 final IInlineSuggestionsRequestCallback callbackImpl =
                         new InlineSuggestionsRequestCallbackDecorator(callback,
                                 imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
                                 this);
-                try {
-                    curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest()", e);
-                }
+                curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
             } else {
                 callback.onInlineSuggestionsUnsupported();
             }
@@ -2190,13 +2181,9 @@
                             mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
-                        IInputMethod curMethod = getCurMethodLocked();
+                        IInputMethodInvoker curMethod = getCurMethodLocked();
                         if (curMethod != null) {
-                            try {
-                                curMethod.unbindInput();
-                            } catch (RemoteException e) {
-                                // There is nothing interesting about the method dying.
-                            }
+                            curMethod.unbindInput();
                         }
                     }
                     mCurClient = null;
@@ -2208,6 +2195,24 @@
         }
     }
 
+    @NonNull
+    private Message obtainMessageOO(int what, Object arg1, Object arg2) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        return mHandler.obtainMessage(what, 0, 0, args);
+    }
+
+    @NonNull
+    private Message obtainMessageIIIO(int what, int argi1, int argi2, int argi3, Object arg1) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.argi1 = argi1;
+        args.argi2 = argi2;
+        args.argi3 = argi3;
+        return mHandler.obtainMessage(what, 0, 0, args);
+    }
+
     private void executeOrSendMessage(IInputMethodClient target, Message msg) {
          if (target.asBinder() instanceof Binder) {
              // This is supposed to be emulating the one-way semantics when the IME client is
@@ -2216,7 +2221,7 @@
              // We probably should create a simple wrapper of IInputMethodClient as the first step
              // to get rid of executeOrSendMessage() then should prohibit system_server to be the
              // IME client for long term.
-             mCaller.sendMessage(msg);
+             msg.sendToTarget();
          } else {
              handleMessage(msg);
              msg.recycle();
@@ -2230,19 +2235,15 @@
                     + mCurClient.client.asBinder());
             if (mBoundToMethod) {
                 mBoundToMethod = false;
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null) {
-                    try {
-                        curMethod.unbindInput();
-                    } catch (RemoteException e) {
-                        // There is nothing interesting about the method dying.
-                    }
+                    curMethod.unbindInput();
                 }
             }
 
             scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
                     false /* reportToImeController */);
-            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+            executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
                     MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason,
                     mCurClient.client));
             mCurClient.sessionRequested = false;
@@ -2285,11 +2286,7 @@
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
         if (!mBoundToMethod) {
-            IInputMethod curMethod = getCurMethodLocked();
-            try {
-                curMethod.bindInput(mCurClient.binding);
-            } catch (RemoteException e) {
-            }
+            getCurMethodLocked().bindInput(mCurClient.binding);
             mBoundToMethod = true;
         }
 
@@ -2316,11 +2313,8 @@
         }
 
         final SessionState session = mCurClient.curSession;
-        try {
-            setEnabledSessionLocked(session);
-            session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
-        } catch (RemoteException e) {
-        }
+        setEnabledSessionLocked(session);
+        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
 
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
@@ -2514,28 +2508,26 @@
     }
 
     @GuardedBy("ImfLock.class")
-    void initializeImeLocked(@NonNull IInputMethod inputMethod, @NonNull IBinder token,
+    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
             @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
         if (DEBUG) {
             Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                     + mCurTokenDisplayId);
         }
-        try {
-            inputMethod.initializeInternal(token,
-                    new InputMethodPrivilegedOperationsImpl(this, token), configChanges,
-                    supportStylusHw);
-        } catch (RemoteException e) {
-        }
+        inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
+                configChanges, supportStylusHw);
     }
 
     @AnyThread
     void scheduleNotifyImeUidToAudioService(int uid) {
-        mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
-        mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
+        mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
+        mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */)
+                .sendToTarget();
     }
 
     @BinderThread
-    void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) {
+    void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
+            InputChannel channel) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
         try {
             synchronized (ImfLock.class) {
@@ -2545,7 +2537,7 @@
                     channel.dispose();
                     return;
                 }
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null && method != null
                         && curMethod.asBinder() == method.asBinder()) {
                     if (mCurClient != null) {
@@ -2555,7 +2547,7 @@
                         InputBindResult res = attachNewInputLocked(
                                 StartInputReason.SESSION_CREATED_BY_IME, true);
                         if (res.method != null) {
-                            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
+                            executeOrSendMessage(mCurClient.client, obtainMessageOO(
                                     MSG_BIND_CLIENT, mCurClient.client, res));
                         }
                         return;
@@ -2609,7 +2601,7 @@
 
             cs.sessionRequested = true;
 
-            final IInputMethod curMethod = getCurMethodLocked();
+            final IInputMethodInvoker curMethod = getCurMethodLocked();
             final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() {
                 @Override
                 public void sessionCreated(IInputMethodSession session) {
@@ -2624,7 +2616,6 @@
 
             try {
                 curMethod.createSession(clientChannel, callback);
-            } catch (RemoteException e) {
             } finally {
                 // Dispose the channel because the remote proxy will get its own copy when
                 // unparceled.
@@ -3012,14 +3003,10 @@
             }
             if (newSubtype != oldSubtype) {
                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null) {
-                    try {
-                        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
-                        curMethod.changeInputMethodSubtype(newSubtype);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
-                    }
+                    updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+                    curMethod.changeInputMethodSubtype(newSubtype);
                 }
             }
             return;
@@ -3096,13 +3083,9 @@
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
-                final IInputMethod curMethod = getCurMethodLocked();
+                final IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null) {
-                    try {
-                        curMethod.canStartStylusHandwriting(++mHwRequestId);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
-                    }
+                    curMethod.canStartStylusHandwriting(++mHwRequestId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -3153,21 +3136,20 @@
         }
 
         mBindingController.setCurrentMethodVisible();
-        final IInputMethod curMethod = getCurMethodLocked();
+        final IInputMethodInvoker curMethod = getCurMethodLocked();
         if (curMethod != null) {
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
             final int showFlags = getImeShowFlagsLocked();
-            try {
-                if (DEBUG) {
-                    Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
-                            + ", " + showFlags + ", " + resultReceiver + ") for reason: "
-                            + InputMethodDebug.softInputDisplayReasonToString(reason));
-                }
-                curMethod.showSoftInput(showInputToken, showFlags, resultReceiver);
+            if (DEBUG) {
+                Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+                        + ", " + showFlags + ", " + resultReceiver + ") for reason: "
+                        + InputMethodDebug.softInputDisplayReasonToString(reason));
+            }
+            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
+            if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
                 onShowHideSoftInputRequested(true /* show */, windowToken, reason);
-            } catch (RemoteException e) {
             }
             mInputShown = true;
             return true;
@@ -3236,7 +3218,7 @@
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
-        IInputMethod curMethod = getCurMethodLocked();
+        IInputMethodInvoker curMethod = getCurMethodLocked();
         final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
                 || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
         boolean res;
@@ -3252,10 +3234,9 @@
                         + ", " + resultReceiver + ") for reason: "
                         + InputMethodDebug.softInputDisplayReasonToString(reason));
             }
-            try {
-                curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver);
+            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
+            if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
                 onShowHideSoftInputRequested(false /* show */, windowToken, reason);
-            } catch (RemoteException e) {
             }
             res = true;
         } else {
@@ -3671,9 +3652,10 @@
 
             // Always call subtype picker, because subtype picker is a superset of input method
             // picker.
-            mHandler.sendMessage(mCaller.obtainMessageII(
-                    MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode,
-                    (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY));
+            final int displayId =
+                    (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY;
+            mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+                    .sendToTarget();
         }
     }
 
@@ -3688,8 +3670,8 @@
         }
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
-        mHandler.sendMessage(mCaller.obtainMessageII(
-                MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId));
+        mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
+                .sendToTarget();
     }
 
     /**
@@ -3946,7 +3928,7 @@
     @Override
     public void removeImeSurface() {
         mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+        mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
     }
 
     @Override
@@ -4160,7 +4142,7 @@
         }
     }
 
-    /** Called right after {@link IInputMethod#showSoftInput}. */
+    /** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */
     @GuardedBy("ImfLock.class")
     private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
             @SoftInputShowHideReason int reason) {
@@ -4215,19 +4197,13 @@
     void setEnabledSessionLocked(SessionState session) {
         if (mEnabledSession != session) {
             if (mEnabledSession != null && mEnabledSession.session != null) {
-                try {
-                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
-                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
-                } catch (RemoteException e) {
-                }
+                if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
+                mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
             }
             mEnabledSession = session;
             if (mEnabledSession != null && mEnabledSession.session != null) {
-                try {
-                    if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
-                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
-                } catch (RemoteException e) {
-                }
+                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
+                mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
             }
         }
     }
@@ -4394,12 +4370,8 @@
                 return;
             }
 
-            try {
-                // TODO: replace null  with actual Channel, MotionEvents
-                getCurMethodLocked().startStylusHandwriting(null, null);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e);
-            }
+            // TODO: replace null  with actual Channel, MotionEvents
+            getCurMethodLocked().startStylusHandwriting(null, null);
         }
     }
 
@@ -4424,8 +4396,8 @@
 
     private void scheduleSetActiveToClient(ClientState state, boolean active, boolean fullscreen,
             boolean reportToImeController) {
-        executeOrSendMessage(state.client, mCaller.obtainMessageIIIIO(MSG_SET_ACTIVE,
-                active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, 0, state));
+        executeOrSendMessage(state.client, obtainMessageIIIO(MSG_SET_ACTIVE,
+                active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, state));
     }
 
     @GuardedBy("ImfLock.class")
@@ -4988,14 +4960,13 @@
 
         @Override
         public void removeImeSurface() {
-            mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+            mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
         }
 
         @Override
         public void updateImeWindowStatus(boolean disableImeIcon) {
-            mService.mHandler.sendMessage(
-                    mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS,
-                            disableImeIcon ? 1 : 0, 0));
+            mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
+                    .sendToTarget();
         }
     }
 
@@ -5061,8 +5032,9 @@
             }
             if (mCurClient != null && mCurClient.client != null) {
                 mInFullscreenMode = fullscreen;
-                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                        MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
+                executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
+                        MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0 /* unused */,
+                        mCurClient));
             }
         }
     }
@@ -5132,7 +5104,7 @@
     @BinderThread
     private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
             boolean isCritical) {
-        IInputMethod method;
+        IInputMethodInvoker method;
         ClientState client;
         ClientState focusedWindowClient;
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index fcb1be0..348bb2d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -16,22 +16,19 @@
 
 package com.android.server.inputmethod;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
 
-import android.app.ActivityThread;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.IBinder;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
-import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -45,7 +42,6 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.wm.WindowManagerInternal;
@@ -53,8 +49,7 @@
 import java.util.List;
 
 /** A controller to show/hide the input method menu */
-@VisibleForTesting(visibility = PACKAGE)
-public class InputMethodMenuController {
+final class InputMethodMenuController {
     private static final String TAG = InputMethodMenuController.class.getSimpleName();
 
     private final InputMethodManagerService mService;
@@ -64,17 +59,18 @@
     private final KeyguardManager mKeyguardManager;
     private final WindowManagerInternal mWindowManagerInternal;
 
-    private Context mSettingsContext;
     private AlertDialog.Builder mDialogBuilder;
     private AlertDialog mSwitchingDialog;
-    private IBinder mSwitchingDialogToken;
     private View mSwitchingDialogTitleView;
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
 
     private boolean mShowImeWithHardKeyboard;
 
-    @VisibleForTesting(visibility = PACKAGE)
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    private InputMethodDialogWindowContext mDialogWindowContext;
+
     public InputMethodMenuController(InputMethodManagerService service) {
         mService = service;
         mSettings = mService.mSettings;
@@ -132,8 +128,11 @@
                 }
             }
 
-            final Context settingsContext = getSettingsContext(displayId);
-            mDialogBuilder = new AlertDialog.Builder(settingsContext);
+            if (mDialogWindowContext == null) {
+                mDialogWindowContext = new InputMethodDialogWindowContext();
+            }
+            final Context dialogWindowContext = mDialogWindowContext.get(displayId);
+            mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
             mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
 
             final Context dialogContext = mDialogBuilder.getContext();
@@ -199,7 +198,7 @@
             // Use an alternate token for the dialog for that window manager can group the token
             // with other IME windows based on type vs. grouping based on whichever token happens
             // to get selected by the system later on.
-            attrs.token = mSwitchingDialogToken;
+            attrs.token = dialogWindowContext.getWindowContextToken();
             attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
@@ -208,27 +207,6 @@
         }
     }
 
-    /**
-     * Returns the window context for IME switch dialogs to receive configuration changes.
-     *
-     * This method initializes the window context if it was not initialized. This method also moves
-     * the context to the targeted display if the current display of context is different than
-     * the display specified by {@code displayId}.
-     */
-    @VisibleForTesting
-    public Context getSettingsContext(int displayId) {
-        if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
-            final Context systemUiContext = ActivityThread.currentActivityThread()
-                    .getSystemUiContext(displayId);
-            final Context windowContext = systemUiContext.createWindowContext(
-                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
-            mSettingsContext = new ContextThemeWrapper(
-                    windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
-            mSwitchingDialogToken = mSettingsContext.getWindowContextToken();
-        }
-        return mSettingsContext;
-    }
-
     private boolean isScreenLocked() {
         return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
                 && mKeyguardManager.isKeyguardSecure();
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 6676987..d48ccd5 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -69,7 +69,7 @@
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -598,7 +598,7 @@
                     0,
                     0,
                     null,
-                    PackageUserState.DEFAULT,
+                    PackageUserStateInternal.DEFAULT,
                     UserHandle.getCallingUserId(),
                     null);
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
new file mode 100644
index 0000000..ff6372ae
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.logcat;
+
+import android.content.Context;
+import android.os.ILogd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.logcat.ILogcatManagerService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Service responsible for manage the access to Logcat.
+ */
+public final class LogcatManagerService extends SystemService {
+
+    private static final String TAG = "LogcatManagerService";
+    private final Context mContext;
+    private final BinderService mBinderService;
+    private final ExecutorService mThreadExecutor;
+    private ILogd mLogdService;
+
+    private final class BinderService extends ILogcatManagerService.Stub {
+        @Override
+        public void startThread(int uid, int gid, int pid, int fd) {
+            mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, true));
+        }
+
+        @Override
+        public void finishThread(int uid, int gid, int pid, int fd) {
+            // TODO This thread will be used to notify the AppOpsManager that
+            // the logd data access is finished.
+            mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
+        }
+    }
+
+    private class LogdMonitor implements Runnable {
+
+        private final int mUid;
+        private final int mGid;
+        private final int mPid;
+        private final int mFd;
+        private final boolean mStart;
+
+        /**
+         * For starting a thread, the start value is true.
+         * For finishing a thread, the start value is false.
+         */
+        LogdMonitor(int uid, int gid, int pid, int fd, boolean start) {
+            mUid = uid;
+            mGid = gid;
+            mPid = pid;
+            mFd = fd;
+            mStart = start;
+        }
+
+        /**
+         * The current version grant the permission by default.
+         * And track the logd access.
+         * The next version will generate a prompt for users.
+         * The users decide whether the logd access is allowed.
+         */
+        @Override
+        public void run() {
+            if (mLogdService == null) {
+                LogcatManagerService.this.addLogdService();
+            }
+
+            if (mStart) {
+                try {
+                    mLogdService.approve(mUid, mGid, mPid, mFd);
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Fails to call remote functions ", ex);
+                }
+            }
+        }
+    }
+
+    public LogcatManagerService(Context context) {
+        super(context);
+        mContext = context;
+        mBinderService = new BinderService();
+        mThreadExecutor = Executors.newCachedThreadPool();
+    }
+
+    @Override
+    public void onStart() {
+        try {
+            publishBinderService("logcat", mBinderService);
+        } catch (Throwable t) {
+            Slog.e(TAG, "Could not start the LogcatManagerService.", t);
+        }
+    }
+
+    private void addLogdService() {
+        mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
+    }
+
+}
diff --git a/services/core/java/com/android/server/logcat/OWNERS b/services/core/java/com/android/server/logcat/OWNERS
new file mode 100644
index 0000000..9588fa9
--- /dev/null
+++ b/services/core/java/com/android/server/logcat/OWNERS
@@ -0,0 +1,5 @@
+cbrubaker@google.com
+eunjeongshin@google.com
+jsharkey@google.com
+vishwath@google.com
+wenhaowang@google.com
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index e555c13..c01851a 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -24,7 +24,6 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
-import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -130,7 +129,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -1010,10 +1008,11 @@
             userFilter.addAction(ACTION_USER_REMOVED);
             mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
 
-            // listen for stats update events
-            final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
-            mContext.registerReceiver(
-                    mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+            // listen for stats updated callbacks for interested network types.
+            mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_MOBILE).build(),
+                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
+            mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_WIFI).build(),
+                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
 
             // Listen for snooze from notifications
             mContext.registerReceiver(mSnoozeReceiver,
@@ -1214,19 +1213,16 @@
     };
 
     /**
-     * Receiver that watches for {@link NetworkStatsManager} updates, which we
-     * use to check against {@link NetworkPolicy#warningBytes}.
+     * Listener that watches for {@link NetworkStatsManager} updates, which
+     * NetworkPolicyManagerService uses to check against {@link NetworkPolicy#warningBytes}.
      */
-    private final NetworkStatsBroadcastReceiver mStatsReceiver =
-            new NetworkStatsBroadcastReceiver();
-    private class NetworkStatsBroadcastReceiver extends BroadcastReceiver {
-        private boolean mIsAnyIntentReceived = false;
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            // on background handler thread, and verified
-            // READ_NETWORK_USAGE_HISTORY permission above.
+    private final StatsCallback mStatsCallback = new StatsCallback();
+    private class StatsCallback extends NetworkStatsManager.UsageCallback {
+        private boolean mIsAnyCallbackReceived = false;
 
-            mIsAnyIntentReceived = true;
+        @Override
+        public void onThresholdReached(int networkType, String subscriberId) {
+            mIsAnyCallbackReceived = true;
 
             synchronized (mNetworkPoliciesSecondLock) {
                 updateNetworkRulesNL();
@@ -1236,11 +1232,11 @@
         }
 
         /**
-         * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received.
+         * Return whether any callback is received.
          * Used to determine if NetworkStatsService is ready.
          */
-        public boolean isAnyIntentReceived() {
-            return mIsAnyIntentReceived;
+        public boolean isAnyCallbackReceived() {
+            return mIsAnyCallbackReceived;
         }
     };
 
@@ -1453,7 +1449,7 @@
 
         // Skip if not ready. NetworkStatsService will block public API calls until it is
         // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
-        if (!mStatsReceiver.isAnyIntentReceived()) return null;
+        if (!mStatsCallback.isAnyCallbackReceived()) return null;
 
         final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end);
         for (final NetworkStats.Bucket entry : stats) {
@@ -5450,7 +5446,7 @@
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         // Skip if not ready. NetworkStatsService will block public API calls until it is
         // ready. To prevent NPMS be blocked on that, skip and fail fast instead.
-        if (!mStatsReceiver.isAnyIntentReceived()) return 0;
+        if (!mStatsCallback.isAnyCallbackReceived()) return 0;
         return mDeps.getNetworkTotalBytes(template, start, end);
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 86b385b..6b27321f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -499,6 +499,7 @@
     private IPlatformCompat mPlatformCompat;
     private ShortcutHelper mShortcutHelper;
     private PermissionHelper mPermissionHelper;
+    private UsageStatsManagerInternal mUsageStatsManagerInternal;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -2092,7 +2093,8 @@
             UserManager userManager,
             NotificationHistoryManager historyManager, StatsManager statsManager,
             TelephonyManager telephonyManager, ActivityManagerInternal ami,
-            MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper) {
+            MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
+            UsageStatsManagerInternal usageStatsManagerInternal) {
         mHandler = handler;
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2110,6 +2112,7 @@
         mPackageManagerClient = packageManagerClient;
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
+        mUsageStatsManagerInternal = usageStatsManagerInternal;
         mAppOps = appOps;
         mAppOpsService = iAppOps;
         try {
@@ -2411,7 +2414,8 @@
                 LocalServices.getService(ActivityManagerInternal.class),
                 createToastRateLimiter(), new PermissionHelper(LocalServices.getService(
                         PermissionManagerServiceInternal.class), AppGlobals.getPackageManager(),
-                        AppGlobals.getPermissionManager(), mEnableAppSettingMigration));
+                        AppGlobals.getPermissionManager(), mEnableAppSettingMigration),
+                LocalServices.getService(UsageStatsManagerInternal.class));
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -7259,6 +7263,8 @@
                     if (index < 0) {
                         mNotificationList.add(r);
                         mUsageStats.registerPostedByApp(r);
+                        mUsageStatsManagerInternal.reportNotificationPosted(r.getSbn().getOpPkg(),
+                                r.getSbn().getUser(), SystemClock.elapsedRealtime());
                         final boolean isInterruptive = isVisuallyInterruptive(null, r);
                         r.setInterruptive(isInterruptive);
                         r.setTextChanged(isInterruptive);
@@ -7266,6 +7272,8 @@
                         old = mNotificationList.get(index);  // Potentially *changes* old
                         mNotificationList.set(index, r);
                         mUsageStats.registerUpdatedByApp(r, old);
+                        mUsageStatsManagerInternal.reportNotificationUpdated(r.getSbn().getOpPkg(),
+                                r.getSbn().getUser(), SystemClock.elapsedRealtime());
                         // Make sure we don't lose the foreground service state.
                         notification.flags |=
                                 old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
@@ -8748,6 +8756,8 @@
             case REASON_APP_CANCEL:
             case REASON_APP_CANCEL_ALL:
                 mUsageStats.registerRemovedByApp(r);
+                mUsageStatsManagerInternal.reportNotificationRemoved(r.getSbn().getOpPkg(),
+                        r.getUser(), SystemClock.elapsedRealtime());
                 break;
         }
 
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index cd4244b..ba003d2 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -36,14 +36,6 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedComponent;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
-import com.android.server.pm.pkg.component.ParsedProvider;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedService;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -62,7 +54,15 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedComponent;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedProviderImpl;
+import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.WatchableImpl;
@@ -380,14 +380,13 @@
                     continue;
                 }
                 // See PM.queryContentProviders()'s javadoc for why we have the metaData parameter.
-                if (metaDataKey != null
-                        && (p.getMetaData() == null || !p.getMetaData().containsKey(metaDataKey))) {
+                if (metaDataKey != null && !p.getMetaData().containsKey(metaDataKey)) {
                     continue;
                 }
                 if (appInfoGenerator == null) {
                     appInfoGenerator = new CachedApplicationInfoGenerator();
                 }
-                final PackageUserState state = ps.getUserStateOrDefault(userId);
+                final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
                 final ApplicationInfo appInfo =
                         appInfoGenerator.generate(pkg, flags, state, userId, ps);
                 if (appInfo == null) {
@@ -424,7 +423,7 @@
             if (pkg == null) {
                 return null;
             }
-            final PackageUserState state = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(
                     pkg, flags, state, userId, ps);
             if (appInfo == null) {
@@ -461,7 +460,7 @@
                 if (appInfoGenerator == null) {
                     appInfoGenerator = new CachedApplicationInfoGenerator();
                 }
-                final PackageUserState state = ps.getUserStateOrDefault(userId);
+                final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
                 final ApplicationInfo appInfo =
                         appInfoGenerator.generate(pkg, 0, state, userId, ps);
                 if (appInfo == null) {
@@ -1537,7 +1536,7 @@
                 }
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             ActivityInfo ai = PackageInfoUtils.generateActivityInfo(pkg, activity, mFlags,
                     userState, userId, ps);
             if (ai == null) {
@@ -1854,7 +1853,7 @@
             if (ps == null) {
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             final boolean matchVisibleToInstantApp = (mFlags
                     & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
             final boolean isInstantApp = (mFlags & PackageManager.MATCH_INSTANT) != 0;
@@ -2099,7 +2098,7 @@
             if (ps == null) {
                 return null;
             }
-            final PackageUserState userState = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
             ServiceInfo si = PackageInfoUtils.generateServiceInfo(pkg, service, mFlags,
                     userState, userId, ps);
             if (si == null) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 69c475a..cca1b97 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -823,7 +823,7 @@
         }
         if (resolveComponentName().equals(component)) {
             return PackageInfoWithoutStateUtils.generateDelegateActivityInfo(mResolveActivity,
-                    flags, PackageUserState.DEFAULT, userId);
+                    flags, PackageUserStateInternal.DEFAULT, userId);
         }
         return null;
     }
@@ -1547,7 +1547,7 @@
             flags |= MATCH_ANY_USER;
         }
 
-        final PackageUserState state = ps.getUserStateOrDefault(userId);
+        final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
         AndroidPackage p = ps.getPkg();
         if (p != null) {
             // Compute GIDs only if requested
@@ -3548,7 +3548,7 @@
             if (shouldFilterApplication(ps, callingUid, userId)) {
                 return false;
             }
-            final PackageUserState state = ps.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             if (state != null) {
                 return PackageUserStateUtils.isAvailable(state, 0);
             }
@@ -4011,7 +4011,7 @@
                     ps, callingUid, component, TYPE_PROVIDER, userId)) {
                 return null;
             }
-            PackageUserState state = ps.getUserStateOrDefault(userId);
+            PackageUserStateInternal state = ps.getUserStateOrDefault(userId);
             final ApplicationInfo appInfo =
                     PackageInfoUtils.generateApplicationInfo(ps.getPkg(), flags, state, userId, ps);
             if (appInfo == null) {
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index a3f1435..a3134a0 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -199,30 +199,6 @@
                     .addDataType("video/*")
                     .build();
 
-    // TODO(b/199068419): Remove once GEM enables the intent for Googlers
-    /** Pick images can be forwarded to work profile. */
-    private static final DefaultCrossProfileIntentFilter ACTION_PICK_IMAGES_TO_PROFILE =
-            new DefaultCrossProfileIntentFilter.Builder(
-                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
-                    /* flags= */ 0,
-                    /* letsPersonalDataIntoProfile= */ true)
-                    .addAction(MediaStore.ACTION_PICK_IMAGES)
-                    .addCategory(Intent.CATEGORY_DEFAULT)
-                    .build();
-    // TODO(b/199068419): Remove once GEM enables the intent for Googlers
-    /** Pick images can be forwarded to work profile. */
-    private static final DefaultCrossProfileIntentFilter
-            ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE =
-            new DefaultCrossProfileIntentFilter.Builder(
-                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
-                    /* flags= */ 0,
-                    /* letsPersonalDataIntoProfile= */ true)
-                    .addAction(MediaStore.ACTION_PICK_IMAGES)
-                    .addCategory(Intent.CATEGORY_DEFAULT)
-                    .addDataType("image/*")
-                    .addDataType("video/*")
-                    .build();
-
     /** Open document intent can be forwarded to parent user. */
     private static final DefaultCrossProfileIntentFilter OPEN_DOCUMENT =
             new DefaultCrossProfileIntentFilter.Builder(
@@ -336,8 +312,6 @@
                 ACTION_PICK_DATA,
                 ACTION_PICK_IMAGES,
                 ACTION_PICK_IMAGES_WITH_DATA_TYPES,
-                ACTION_PICK_IMAGES_TO_PROFILE,
-                ACTION_PICK_IMAGES_WITH_DATA_TYPES_TO_PROFILE,
                 OPEN_DOCUMENT,
                 GET_CONTENT,
                 USB_DEVICE_ATTACHED,
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 9efe81a..06405ae 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -30,8 +30,10 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
 
 import android.annotation.Nullable;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -91,6 +93,29 @@
         mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
     }
 
+    private List<File> getFrameworkResApkSplitFiles() {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
+        try {
+            final List<File> splits = new ArrayList<>();
+            final List<ApexManager.ActiveApexInfo> activeApexInfos =
+                    mPm.mApexManager.getActiveApexInfos();
+            for (int i = 0; i < activeApexInfos.size(); i++) {
+                ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
+                File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
+                if (splitsFolder.isDirectory()) {
+                    for (File file : splitsFolder.listFiles()) {
+                        if (ApkLiteParseUtils.isApkFile(file)) {
+                            splits.add(file);
+                        }
+                    }
+                }
+            }
+            return splits;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
         scanPartitions.addAll(mPm.mInjector.getSystemPartitions());
@@ -184,7 +209,8 @@
         if (!mPm.isOnlyCoreApps()) {
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                     SystemClock.uptimeMillis());
-            scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0,
+            scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+                    mScanFlags | SCAN_REQUIRE_KNOWN, 0,
                     packageParser, executorService);
 
         }
@@ -247,12 +273,14 @@
             if (partition.getOverlayFolder() == null) {
                 continue;
             }
-            scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+            scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
                     packageParser, executorService);
         }
 
-        scanDirTracedLI(frameworkDir, mSystemParseFlags,
+        List<File> frameworkSplits = getFrameworkResApkSplitFiles();
+        scanDirTracedLI(frameworkDir, frameworkSplits,
+                mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
                 mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
@@ -263,12 +291,13 @@
         for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
             final ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.getPrivAppFolder() != null) {
-                scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags,
+                scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+                        mSystemParseFlags,
                         mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                         packageParser, executorService);
             }
-            scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags,
-                    mSystemScanFlags | partition.scanFlag, 0,
+            scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+                    mSystemParseFlags, mSystemScanFlags | partition.scanFlag, 0,
                     packageParser, executorService);
         }
     }
@@ -285,12 +314,13 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
+    private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+            final int parseFlags, int scanFlags,
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
-                    currentTime, packageParser, executorService);
+            mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+                    scanFlags, currentTime, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 80699ac..d98626f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1806,10 +1806,8 @@
                 final PackageSetting ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
                 if (ps != null && ps.isPrivileged()) {
                     fsverityCandidates.put(pkg.getBaseApkPath(), null);
-                    if (pkg.getSplitCodePaths() != null) {
-                        for (String splitPath : pkg.getSplitCodePaths()) {
-                            fsverityCandidates.put(splitPath, null);
-                        }
+                    for (String splitPath : pkg.getSplitCodePaths()) {
+                        fsverityCandidates.put(splitPath, null);
                     }
                 }
             }
@@ -1825,15 +1823,13 @@
                 fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
             }
 
-            if (pkg.getSplitCodePaths() != null) {
-                for (String path : pkg.getSplitCodePaths()) {
-                    fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
+            for (String path : pkg.getSplitCodePaths()) {
+                fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
 
-                    final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
-                    if (new File(splitDmPath).exists()) {
-                        fsverityCandidates.put(splitDmPath,
-                                VerityUtils.getFsveritySignatureFilePath(splitDmPath));
-                    }
+                final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
+                if (new File(splitDmPath).exists()) {
+                    fsverityCandidates.put(splitDmPath,
+                            VerityUtils.getFsveritySignatureFilePath(splitDmPath));
                 }
             }
         }
@@ -1989,9 +1985,7 @@
                             oldCodePaths = new ArraySet<>();
                         }
                         Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
-                        if (oldPackage.getSplitCodePaths() != null) {
-                            Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
-                        }
+                        Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
                         ps1.setOldCodePaths(oldCodePaths);
                     } else {
                         ps1.setOldCodePaths(null);
@@ -3412,8 +3406,9 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
-            long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
+    public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+            int scanFlags, long currentTime, PackageParser2 packageParser,
+            ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             Log.d(TAG, "No files in app dir " + scanDir);
@@ -3425,7 +3420,7 @@
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
         ParallelPackageParser parallelPackageParser =
-                new ParallelPackageParser(packageParser, executorService);
+                new ParallelPackageParser(packageParser, executorService, frameworkSplits);
 
         // Submit files for parsing in parallel
         int fileCount = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e00f4f5..e0d404a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -232,7 +232,7 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
@@ -8765,7 +8765,7 @@
             // The instance created in PackageManagerService is special cased to be non-user
             // specific, so initialize all the needed fields here.
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+                    PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
             // Set up information for custom user intent resolution activity.
             mResolveActivity.applicationInfo = appInfo;
@@ -8797,7 +8797,7 @@
             // The instance stored in PackageManagerService is special cased to be non-user
             // specific, so initialize all the needed fields here.
             mAndroidApplication = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserState.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
+                    PackageUserStateInternal.DEFAULT, UserHandle.USER_SYSTEM, pkgSetting);
 
             if (!mResolverReplaced) {
                 mResolveActivity.applicationInfo = mAndroidApplication;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index e03cf0a..1d2b829 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -228,11 +228,9 @@
         }
         final File baseFile = new File(pkg.getBaseApkPath());
         long maxModifiedTime = baseFile.lastModified();
-        if (pkg.getSplitCodePaths() != null) {
-            for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
-                final File splitFile = new File(pkg.getSplitCodePaths()[i]);
-                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-            }
+        for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) {
+            final File splitFile = new File(pkg.getSplitCodePaths()[i]);
+            maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
         }
         return maxModifiedTime;
     }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9dbf57d..5fc840c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -295,14 +295,12 @@
             proto.write(PackageProto.SplitProto.REVISION_CODE, pkg.getBaseRevisionCode());
             proto.end(splitToken);
 
-            if (pkg.getSplitNames() != null) {
-                for (int i = 0; i < pkg.getSplitNames().length; i++) {
-                    splitToken = proto.start(PackageProto.SPLITS);
-                    proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
-                    proto.write(PackageProto.SplitProto.REVISION_CODE,
-                            pkg.getSplitRevisionCodes()[i]);
-                    proto.end(splitToken);
-                }
+            for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                splitToken = proto.start(PackageProto.SPLITS);
+                proto.write(PackageProto.SplitProto.NAME, pkg.getSplitNames()[i]);
+                proto.write(PackageProto.SplitProto.REVISION_CODE,
+                        pkg.getSplitRevisionCodes()[i]);
+                proto.end(splitToken);
             }
 
             long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
@@ -1263,8 +1261,8 @@
 
     @Nullable
     @Override
-    public Integer getSharedUserId() {
-        return sharedUser == null ? null : sharedUser.userId;
+    public int getSharedUserId() {
+        return sharedUser == null ? -1 : sharedUser.userId;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 5625884..45030bf 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,6 +27,7 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
+import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
@@ -54,9 +55,17 @@
 
     private final ExecutorService mExecutorService;
 
+    private final List<File> mFrameworkSplits;
+
     ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
+        this(packageParser, executorService, /* frameworkSplits= */ null);
+    }
+
+    ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
+            List<File> frameworkSplits) {
         mPackageParser = packageParser;
         mExecutorService = executorService;
+        mFrameworkSplits = frameworkSplits;
     }
 
     static class ParseResult {
@@ -125,6 +134,6 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 45837717..f21bc93 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4245,7 +4245,7 @@
         final PackageSetting ps = mPackages.get(componentInfo.packageName);
         if (ps == null) return false;
 
-        final PackageUserState userState = ps.readUserState(userId);
+        final PackageUserStateInternal userState = ps.readUserState(userId);
         return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
     }
 
@@ -4255,7 +4255,7 @@
         final PackageSetting ps = mPackages.get(component.getPackageName());
         if (ps == null) return false;
 
-        final PackageUserState userState = ps.readUserState(userId);
+        final PackageUserStateInternal userState = ps.readUserState(userId);
         return PackageUserStateUtils.isMatch(userState, pkg.isSystem(), pkg.isEnabled(), component,
                 flags);
     }
@@ -4497,13 +4497,11 @@
                 pw.print(checkinTag); pw.print("-"); pw.print("splt,");
                 pw.print("base,");
                 pw.println(pkg.getBaseRevisionCode());
-                if (pkg.getSplitNames() != null) {
-                    int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
-                    for (int i = 0; i < pkg.getSplitNames().length; i++) {
-                        pw.print(checkinTag); pw.print("-"); pw.print("splt,");
-                        pw.print(pkg.getSplitNames()[i]); pw.print(",");
-                        pw.println(splitRevisionCodes[i]);
-                    }
+                int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
+                for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                    pw.print(checkinTag); pw.print("-"); pw.print("splt,");
+                    pw.print(pkg.getSplitNames()[i]); pw.print(",");
+                    pw.println(splitRevisionCodes[i]);
                 }
             }
             for (UserInfo user : users) {
@@ -5169,13 +5167,11 @@
             }
             String[] splitNames = pkg.getSplitNames();
             int[] splitRevisionCodes = pkg.getSplitRevisionCodes();
-            if (splitNames != null) {
-                for (int i = 0; i < splitNames.length; i++) {
-                    pw.print(", ");
-                    pw.print(splitNames[i]);
-                    if (splitRevisionCodes[i] != 0) {
-                        pw.print(":"); pw.print(splitRevisionCodes[i]);
-                    }
+            for (int i = 0; i < splitNames.length; i++) {
+                pw.print(", ");
+                pw.print(splitNames[i]);
+                if (splitRevisionCodes[i] != 0) {
+                    pw.print(":"); pw.print(splitRevisionCodes[i]);
                 }
             }
             pw.print("]");
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index fc88af9..beea86d 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -78,7 +78,7 @@
 
         String baseApkContextClassLoader = encodeClassLoader(
                 "", pkg.getClassLoaderName(), sharedLibrariesContext);
-        if (pkg.getSplitCodePaths() == null) {
+        if (ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
             // The application has no splits.
             return new String[] {baseApkContextClassLoader};
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/services/core/java/com/android/server/pm/package-info.java
similarity index 64%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
rename to services/core/java/com/android/server/pm/package-info.java
index 2b3e961..04b8e0a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/services/core/java/com/android/server/pm/package-info.java
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
-
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
-
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.server.pm;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 0fa0dc3..2d0a3ef 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -59,7 +59,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import libcore.util.EmptyArray;
 
@@ -85,8 +85,8 @@
     @Nullable
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
-            long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
                 grantedPermissions, state, userId, null, pkgSetting);
     }
@@ -98,7 +98,7 @@
     public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, int flags,
             @Nullable PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
+                PackageUserStateInternal.DEFAULT, UserHandle.getCallingUserId(), apexInfo, pkgSetting);
     }
 
     /**
@@ -106,8 +106,9 @@
      */
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
-            long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
-            @Nullable ApexInfo apexInfo, @Nullable PackageStateInternal pkgSetting) {
+            long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable ApexInfo apexInfo,
+            @Nullable PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
                 pkgSetting);
         if (applicationInfo == null) {
@@ -209,8 +210,9 @@
      */
     @Nullable
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
-            @PackageManager.ApplicationInfoFlagsBits long flags, @NonNull PackageUserState state,
-            int userId, @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ApplicationInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @UserIdInt int userId,
+            @Nullable PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
         }
@@ -255,7 +257,8 @@
      */
     @Nullable
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @UserIdInt int userId,
             @Nullable PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
@@ -265,9 +268,9 @@
      */
     @Nullable
     private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
-            @Nullable ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -291,8 +294,8 @@
      */
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
+            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
 
@@ -301,7 +304,7 @@
      */
     @Nullable
     private static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @Nullable ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (s == null) return null;
@@ -326,7 +329,7 @@
      */
     @Nullable
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @NonNull ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (p == null) return null;
@@ -418,11 +421,11 @@
     }
 
     /**
-     * Returns true if the package is installed and not hidden, or if the caller
-     * explicitly wanted all uninstalled and hidden packages as well.
+     * Returns true if the package is installed and not hidden, or if the caller explicitly wanted
+     * all uninstalled and hidden packages as well.
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageUserState state,
+            PackageStateInternal pkgSetting, PackageUserStateInternal state,
             @PackageManager.PackageInfoFlagsBits long flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
@@ -466,7 +469,9 @@
         return hasFlag ? flag : 0;
     }
 
-    /** @see ApplicationInfo#flags */
+    /**
+     * @see ApplicationInfo#flags
+     */
     public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
         // @formatter:off
         int pkgWithoutStateFlags = PackageInfoWithoutStateUtils.appInfoFlags(pkg)
@@ -628,7 +633,7 @@
          */
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
-                @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state,
+                @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
                 int userId, @Nullable PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 08e2f7d..b2e15e7 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -22,9 +22,6 @@
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -41,6 +38,9 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.io.File;
 import java.util.List;
@@ -147,6 +147,15 @@
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
             throws PackageManagerException {
+        return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
+    }
+
+    /**
+     * TODO(b/135203078): Document new package parsing
+     */
+    @AnyThread
+    public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
+            List<File> frameworkSplits) throws PackageManagerException {
         if (useCaches && mCacher != null) {
             ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
             if (parsed != null) {
@@ -156,7 +165,8 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
+                frameworkSplits);
         if (result.isError()) {
             throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 9b3d6d6..8e41c9b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -73,11 +73,6 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.SigningDetails;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedPermission;
-import com.android.server.pm.pkg.component.ParsedPermissionGroup;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.metrics.LogMaker;
 import android.os.AsyncTask;
@@ -136,6 +131,10 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedPermissionGroup;
+import com.android.server.pm.pkg.component.ParsedPermissionUtils;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
@@ -3172,18 +3171,17 @@
                 }
             } else if (NOTIFICATION_PERMISSIONS.contains(newPerm)) {
                 //&& (origPs.getPermissionState(newPerm) == null) {
-                // TODO(b/205888750): add back line about origPs once propagated through droidfood
+                // TODO(b/205888750): add back line about origPs once all TODO sections below are
+                //  propagated through droidfood
                 Permission bp = mRegistry.getPermission(newPerm);
                 if (bp == null) {
                     throw new IllegalStateException("Unknown new permission " + newPerm);
                 }
-                // TODO(b/205888750): remove the line for REVOKE_WHEN_REQUESTED once propagated
-                //  through droidfood
                 if (!isUserSetOrPregrantedOrFixed(ps.getPermissionFlags(newPerm))) {
                     updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                    ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                                    | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                            PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED);
+                    int setFlag = ps.isPermissionGranted(newPerm)
+                            ? 0 : FLAG_PERMISSION_REVIEW_REQUIRED;
+                    ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVIEW_REQUIRED, setFlag);
                     // TODO(b/205888750): remove if/else block once propagated through droidfood
                     if (ps.isPermissionGranted(newPerm)
                             && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
@@ -3192,6 +3190,10 @@
                             && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
                         ps.grantPermission(bp);
                     }
+                } else {
+                    // TODO(b/205888750): remove once propagated through droidfood
+                    ps.updatePermissionFlags(bp, FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+                            | FLAG_PERMISSION_REVIEW_REQUIRED, 0);
                 }
             }
         }
@@ -4779,9 +4781,10 @@
 
         // Handle REVIEW_REQUIRED
         if ((newFlags & priorityFixedMask) == 0) {
-            if (NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
+            if ((newFlags & (defaultGrantMask | userSettableMask)) == 0
+                    && NOTIFICATION_PERMISSIONS.contains(srcState.getName())) {
                 // For notification permissions, inherit from both states
-                // if no priority FIXED flags are set
+                // if no priority FIXED or DEFAULT_GRANT or USER_SET flags are set
                 newFlags |= (combinedFlags & FLAG_PERMISSION_REVIEW_REQUIRED);
             } else if ((newFlags & priorityMask) == 0) {
                 // Else inherit from destState if no priority flags are set
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
index 3fde41a..d3c8c7b 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackageApi.java
@@ -16,21 +16,337 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.util.SparseArray;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PkgAppInfo;
-import com.android.server.pm.parsing.pkg.PkgPackageInfo;
+import com.android.server.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedPermission;
+import com.android.server.pm.pkg.component.ParsedProvider;
+import com.android.server.pm.pkg.component.ParsedService;
+
+import java.util.List;
 
 /**
  * Explicit interface used for consumers like mainline who need a {@link SystemApi @SystemApi} form
- * of {@link AndroidPackage}.
- * <p>
- * There should be no methods in this class. All of them must come from other interfaces that group
- * the actual methods. This is done to ensure proper separation of the (legacy?) Info object APIs.
+ * of {@link AndroidPackage}. *
+ * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public interface AndroidPackageApi extends PkgPackageInfo, PkgAppInfo {
+public interface AndroidPackageApi {
 
+    boolean areAttributionsUserVisible();
+
+    @Nullable
+    String getAppComponentFactory();
+
+    int getAutoRevokePermissions();
+
+    @Nullable
+    String getBackupAgentName();
+
+    int getBanner();
+
+    @NonNull
+    String getBaseApkPath();
+
+    int getCategory();
+
+    @Nullable
+    String getClassLoaderName();
+
+    @Nullable
+    String getClassName();
+
+    int getCompatibleWidthLimitDp();
+
+    int getDataExtractionRules();
+
+    int getDescriptionRes();
+
+    int getFullBackupContent();
+
+    int getGwpAsanMode();
+
+    int getIconRes();
+
+    int getInstallLocation();
+
+    int getLabelRes();
+
+    int getLargestWidthLimitDp();
+
+    int getLogo();
+
+    @Nullable
+    String getManageSpaceActivityName();
+
+    float getMaxAspectRatio();
+
+    int getMemtagMode();
+
+    float getMinAspectRatio();
+
+    int getMinSdkVersion();
+
+    int getNativeHeapZeroInitialized();
+
+    int getNetworkSecurityConfigRes();
+
+    @Nullable
+    CharSequence getNonLocalizedLabel();
+
+    @NonNull
+    String getPath();
+
+    @Nullable
+    String getPermission();
+
+    @NonNull
+    String getProcessName();
+
+    int getRequiresSmallestWidthDp();
+
+    @SuppressLint("AutoBoxing")
+    @Nullable
+    Boolean getResizeableActivity();
+
+    int getRoundIconRes();
+
+    @NonNull
+    String[] getSplitClassLoaderNames();
+
+    @NonNull
+    String[] getSplitCodePaths();
+
+    @Nullable
+    SparseArray<int[]> getSplitDependencies();
+
+    int getTargetSdkVersion();
+
+    int getTargetSandboxVersion();
+
+    @Nullable
+    String getTaskAffinity();
+
+    int getTheme();
+
+    int getUiOptions();
+
+    @Nullable
+    String getVolumeUuid();
+
+    @Nullable
+    String getZygotePreloadName();
+
+    boolean hasRequestForegroundServiceExemption();
+
+    @SuppressLint("AutoBoxing")
+    @Nullable
+    Boolean hasRequestRawExternalStorageAccess();
+
+    boolean isAllowAudioPlaybackCapture();
+
+    boolean isAllowBackup();
+
+    boolean isAllowClearUserData();
+
+    boolean isAllowClearUserDataOnFailedRestore();
+
+    boolean isAllowNativeHeapPointerTagging();
+
+    boolean isAllowTaskReparenting();
+
+    boolean isAnyDensity();
+
+    boolean isBackupInForeground();
+
+    boolean isBaseHardwareAccelerated();
+
+    boolean isCantSaveState();
+
+    boolean isCrossProfile();
+
+    boolean isDebuggable();
+
+    boolean isDefaultToDeviceProtectedStorage();
+
+    boolean isDirectBootAware();
+
+    boolean isEnabled();
+
+    boolean isExternalStorage();
+
+    boolean isExtractNativeLibs();
+
+    boolean isFullBackupOnly();
+
+    boolean isHasCode();
+
+    boolean isHasDomainUrls();
+
+    boolean isHasFragileUserData();
+
+    boolean isIsolatedSplitLoading();
+
+    boolean isKillAfterRestore();
+
+    boolean isLargeHeap();
+
+    boolean isMultiArch();
+
+    boolean isOverlay();
+
+    boolean isPartiallyDirectBootAware();
+
+    boolean isPersistent();
+
+    boolean isProfileable();
+
+    boolean isProfileableByShell();
+
+    boolean isRequestLegacyExternalStorage();
+
+    boolean isResizeable();
+
+    boolean isResizeableActivityViaSdkVersion();
+
+    boolean isRestoreAnyVersion();
+
+    boolean isStaticSharedLibrary();
+
+    boolean isSdkLibrary();
+
+    boolean isSupportsExtraLargeScreens();
+
+    boolean isSupportsLargeScreens();
+
+    boolean isSupportsNormalScreens();
+
+    boolean isSupportsRtl();
+
+    boolean isSupportsSmallScreens();
+
+    boolean isTestOnly();
+
+    boolean isUseEmbeddedDex();
+
+    boolean isUsesCleartextTraffic();
+
+    boolean isUsesNonSdkApi();
+
+    boolean isVmSafeMode();
+
+    @NonNull
+    List<ParsedActivity> getActivities();
+
+    @NonNull
+    List<ParsedAttribution> getAttributions();
+
+    @NonNull
+    List<String> getAdoptPermissions();
+
+    int getBaseRevisionCode();
+
+    int getCompileSdkVersion();
+
+    @Nullable
+    String getCompileSdkVersionCodeName();
+
+    @NonNull
+    List<ConfigurationInfo> getConfigPreferences();
+
+    @NonNull
+    List<FeatureGroupInfo> getFeatureGroups();
+
+    @NonNull
+    List<ParsedInstrumentation> getInstrumentations();
+
+    long getLongVersionCode();
+
+    @NonNull
+    String getPackageName();
+
+    @NonNull
+    List<ParsedPermission> getPermissions();
+
+    @NonNull
+    List<ParsedProvider> getProviders();
+
+    @NonNull
+    List<ParsedActivity> getReceivers();
+
+    @NonNull
+    List<FeatureInfo> getRequestedFeatures();
+
+    @NonNull
+    List<String> getRequestedPermissions();
+
+    @Nullable
+    String getRequiredAccountType();
+
+    @Nullable
+    String getRestrictedAccountType();
+
+    @NonNull
+    List<ParsedService> getServices();
+
+    @Nullable
+    String getSharedUserId();
+
+    int getSharedUserLabel();
+
+    @NonNull
+    String[] getSplitNames();
+
+    @NonNull
+    int[] getSplitRevisionCodes();
+
+    @Nullable
+    String getVersionName();
+
+    boolean isRequiredForAllUsers();
+
+    @Nullable
+    String getNativeLibraryDir();
+
+    @Nullable
+    String getNativeLibraryRootDir();
+
+    @Nullable
+    String getSecondaryNativeLibraryDir();
+
+    int getUid();
+
+    boolean isFactoryTest();
+
+    boolean isNativeLibraryRootRequiresIsa();
+
+    boolean isOdm();
+
+    boolean isOem();
+
+    boolean isPrivileged();
+
+    boolean isProduct();
+
+    boolean isSignedWithPlatformKey();
+
+    boolean isSystem();
+
+    boolean isSystemExt();
+
+    boolean isVendor();
+
+    boolean isCoreApp();
+
+    boolean isStub();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index f5ee8d9..9fa9644 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -54,7 +54,6 @@
  *
  * @hide
  */
-// TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageState {
 
@@ -163,10 +162,9 @@
      * Retrieves the shared user ID. Note that the actual shared user data is not available here and
      * must be queried separately.
      *
-     * @return the shared user this package is a part of, or null if it's not part of a shared user.
+     * @return the shared user this package is a part of, or -1 if it's not part of a shared user.
      */
-    @Nullable
-    Integer getSharedUserId();
+    int getSharedUserId();
 
     @NonNull
     SigningInfo getSigningInfo();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index a5d399e..9395ca5 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -127,7 +127,7 @@
     @Nullable
     private final String mSecondaryCpuAbi;
     @Nullable
-    private final Integer mSharedUserId;
+    private final int mSharedUserId;
     @NonNull
     private final String[] mUsesSdkLibraries;
     @NonNull
@@ -615,7 +615,7 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable Integer getSharedUserId() {
+    public @Nullable int getSharedUserId() {
         return mSharedUserId;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
index 656c445..91e9b2f 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java
@@ -58,7 +58,7 @@
             ComponentInfo componentInfo, long flags, int userId) {
         if (packageState == null) return false;
 
-        final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         return PackageUserStateUtils.isMatch(userState, componentInfo, flags);
     }
 
@@ -72,7 +72,7 @@
         if (pkg == null) {
             return false;
         }
-        final PackageUserState userState = packageState.getUserStateOrDefault(userId);
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         return PackageUserStateUtils.isMatch(userState, packageState.isSystem(),
                 pkg.isEnabled(), component, flags);
     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index d47c5ec..bed1401 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.pm.PackageManager;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
@@ -30,15 +32,20 @@
  *
  * The parent of this class is {@link PackageState}, which handles non-user state, exposing this
  * interface for per-user state.
+ *
+ * @hide
  */
 // TODO(b/173807334): Expose API
 //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageUserState {
 
+    /** @hide */
+    @NonNull
     PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT;
 
     /**
      * Combination of {@link #getOverlayPaths()} and {@link #getSharedLibraryOverlayPaths()}
+     * @hide
      */
     @Nullable
     OverlayPaths getAllOverlayPaths();
@@ -84,9 +91,11 @@
     @Nullable
     String getLastDisableAppCaller();
 
+    /** @hide */
     @Nullable
     OverlayPaths getOverlayPaths();
 
+    /** @hide */
     @NonNull
     Map<String, OverlayPaths> getSharedLibraryOverlayPaths();
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index bd8b3ab..bc521ce 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.pm.pkg.FrameworkPackageUserState;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -28,7 +29,7 @@
  * still read-only and should be used inside system server code when possible over the
  * implementation.
  */
-public interface PackageUserStateInternal extends PackageUserState {
+public interface PackageUserStateInternal extends PackageUserState, FrameworkPackageUserState {
 
     PackageUserStateInternal DEFAULT = new PackageUserStateDefault();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
index 6d978c4..c2b3cbc 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java
@@ -21,11 +21,13 @@
 import android.content.pm.ActivityInfo;
 
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedActivity extends ParsedMainComponent {
 
     /**
      * Generate activity object that forwards user to App Details page automatically.
      * This activity should be invisible to user and user should not know or see it.
+     * @hide
      */
     @NonNull
     static ParsedActivity makeAppDetailsActivity(String packageName, String processName,
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
index ff97c13..91c0b07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
@@ -20,16 +20,16 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
+import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -37,11 +37,15 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
-/** @hide **/
+/**
+ * @hide
+ **/
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity {
+public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity,
+        Parcelable {
 
     private int theme;
     private int uiOptions;
@@ -112,8 +116,8 @@
     }
 
     /**
-     * Generate activity object that forwards user to App Details page automatically.
-     * This activity should be invisible to user and user should not know or see it.
+     * Generate activity object that forwards user to App Details page automatically. This activity
+     * should be invisible to user and user should not know or see it.
      */
     @NonNull
     static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName,
@@ -641,10 +645,10 @@
     }
 
     @DataClass.Generated(
-            time = 1630600615936L,
+            time = 1641431949361L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull android.content.pm.parsing.component.ParsedActivityImpl makeAlias(java.lang.String,android.content.pm.parsing.component.ParsedActivity)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedActivityImpl setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index db8815e..a6c22a18 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -18,8 +18,9 @@
 
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+
 import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,9 +28,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
 import android.content.pm.parsing.result.ParseResult;
@@ -49,6 +47,9 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -353,10 +354,10 @@
 
             final ParseResult result;
             if (parser.getName().equals("intent-filter")) {
-                ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+                ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
                         !isReceiver, visibleToEphemeral, resources, parser, input);
                 if (intentResult.isSuccess()) {
-                    ParsedIntentInfo intentInfo = intentResult.getResult();
+                    ParsedIntentInfoImpl intentInfo = intentResult.getResult();
                     if (intentInfo != null) {
                         IntentFilter intentFilter = intentInfo.getIntentFilter();
                         activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
@@ -386,11 +387,11 @@
             } else if (parser.getName().equals("property")) {
                 result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
             } else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
-                ParseResult<ParsedIntentInfo> intentResult = parseIntentFilter(pkg, activity,
+                ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
                         true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
                         resources, parser, input);
                 if (intentResult.isSuccess()) {
-                    ParsedIntentInfo intent = intentResult.getResult();
+                    ParsedIntentInfoImpl intent = intentResult.getResult();
                     if (intent != null) {
                         pkg.addPreferredActivityFilter(activity.getClassName(), intent);
                     }
@@ -413,7 +414,7 @@
         }
 
         if (!isAlias && activity.getLaunchMode() != LAUNCH_SINGLE_INSTANCE_PER_TASK
-                && activity.getMetaData() != null && activity.getMetaData().containsKey(
+                && activity.getMetaData().containsKey(
                 ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE)) {
             final String launchMode = activity.getMetaData().getString(
                     ParsingPackageUtils.METADATA_ACTIVITY_LAUNCH_MODE);
@@ -427,7 +428,7 @@
             // set to false.
             boolean canDisplayOnRemoteDevices = array.getBoolean(
                     R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true);
-            if (activity.getMetaData() != null && !activity.getMetaData().getBoolean(
+            if (!activity.getMetaData().getBoolean(
                     ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) {
                 canDisplayOnRemoteDevices = false;
             }
@@ -463,11 +464,11 @@
     }
 
     @NonNull
-    private static ParseResult<ParsedIntentInfo> parseIntentFilter(ParsingPackage pkg,
+    private static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(ParsingPackage pkg,
             ParsedActivityImpl activity, boolean allowImplicitEphemeralVisibility,
             boolean visibleToEphemeral, Resources resources, XmlResourceParser parser,
             ParseInput input) throws IOException, XmlPullParserException {
-        ParseResult<ParsedIntentInfo> result = ParsedMainComponentUtils.parseIntentFilter(activity,
+        ParseResult<ParsedIntentInfoImpl> result = ParsedMainComponentUtils.parseIntentFilter(activity,
                 pkg, resources, parser, visibleToEphemeral, true /*allowGlobs*/,
                 true /*allowAutoVerify*/, allowImplicitEphemeralVisibility,
                 true /*failOnNoActions*/, input);
@@ -475,7 +476,7 @@
             return input.error(result);
         }
 
-        ParsedIntentInfo intent = result.getResult();
+        ParsedIntentInfoImpl intent = result.getResult();
         if (intent != null) {
             final IntentFilter intentFilter = intent.getIntentFilter();
             if (intentFilter.isVisibleToInstantApp()) {
@@ -574,7 +575,7 @@
     private static ParseResult<ActivityInfo.WindowLayout> resolveActivityWindowLayout(
             ParsedActivity activity, ParseInput input) {
         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
-        if (activity.getMetaData() == null || !activity.getMetaData().containsKey(
+        if (!activity.getMetaData().containsKey(
                 ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
             return input.success(activity.getWindowLayout());
         }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
index 7690818..586d2c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java
@@ -18,10 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcelable;
 
 /** @hide */
-public interface ParsedApexSystemService extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedApexSystemService {
 
     @NonNull
     String getName();
@@ -34,5 +34,4 @@
 
     @Nullable
     String getMaxSdkVersion();
-
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
index 8c4d8f0..1e427d0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -19,15 +19,18 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
-/** @hide **/
+/**
+ * @hide
+ **/
 @DataClass(genGetters = true, genAidl = false, genSetters = true, genParcelable = true)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
+public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Parcelable {
 
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
     @NonNull
@@ -49,6 +52,7 @@
     }
 
 
+
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
@@ -213,8 +217,8 @@
     }
 
     @DataClass.Generated.Member
-    public static final @NonNull android.os.Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
-            = new android.os.Parcelable.Creator<ParsedApexSystemServiceImpl>() {
+    public static final @NonNull Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
+            = new Parcelable.Creator<ParsedApexSystemServiceImpl>() {
         @Override
         public ParsedApexSystemServiceImpl[] newArray(int size) {
             return new ParsedApexSystemServiceImpl[size];
@@ -227,10 +231,10 @@
     };
 
     @DataClass.Generated(
-            time = 1638903241144L,
+            time = 1641431950080L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
index 3b91f28..1a5d110 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.StringRes;
-import android.os.Parcelable;
 
 import java.util.List;
 
@@ -28,7 +27,8 @@
  *
  * @hide
  */
-public interface ParsedAttribution extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedAttribution {
 
     /**
      * Maximum length of attribution tag
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
index a4eb4f1..b59f511 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
@@ -35,7 +35,7 @@
  */
 @DataClass(genAidl = false, genSetters = true, genBuilder = false, genParcelable = true)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedAttributionImpl implements ParsedAttribution {
+public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
 
     /** Maximum amount of attributions per package */
     static final int MAX_NUM_ATTRIBUTIONS = 10000;
@@ -206,10 +206,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627594502974L,
+            time = 1641431950829L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java",
-            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
+            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
index 1a8230d..5b6ecba 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java
@@ -21,13 +21,13 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager.Property;
 import android.os.Bundle;
-import android.os.Parcelable;
 
 import java.util.List;
 import java.util.Map;
 
 /** @hide */
-public interface ParsedComponent extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedComponent {
 
     int getBanner();
 
@@ -47,7 +47,7 @@
 
     int getLogo();
 
-    @Nullable
+    @NonNull
     Bundle getMetaData();
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
index 9125e8c..c1a4b2e7 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
@@ -25,28 +25,31 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
-import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-/** @hide */
-@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false)
+/**
+ * @hide
+ */
+@DataClass(genGetters = true, genSetters = true, genConstructor = false, genBuilder = false,
+        genParcelable = false)
 @DataClass.Suppress({"setComponentName", "setProperties", "setIntents"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public abstract class ParsedComponentImpl implements ParsedComponent {
+public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable {
 
     @NonNull
     @DataClass.ParcelWith(ForInternedString.class)
@@ -68,7 +71,7 @@
 
     @NonNull
     @DataClass.PluralOf("intent")
-    private List<ParsedIntentInfo> intents = Collections.emptyList();
+    private List<ParsedIntentInfoImpl> intents = Collections.emptyList();
 
     @Nullable
     private ComponentName componentName;
@@ -95,16 +98,18 @@
         this.flags = other.getFlags();
         this.packageName = other.getPackageName();
         this.componentName = other.getComponentName();
-        this.intents = new ArrayList<>(other.getIntents());
+        this.intents = new ArrayList<>(((ParsedComponentImpl) other).intents);
         this.mProperties = new ArrayMap<>();
         this.mProperties.putAll(other.getProperties());
     }
 
-    public void addIntent(ParsedIntentInfo intent) {
+    public void addIntent(ParsedIntentInfoImpl intent) {
         this.intents = CollectionUtils.add(this.intents, intent);
     }
 
-    /** Add a property to the component */
+    /**
+     * Add a property to the component
+     */
     public void addProperty(@NonNull Property property) {
         this.mProperties = CollectionUtils.add(this.mProperties, property.getName(), property);
     }
@@ -133,6 +138,18 @@
         return componentName;
     }
 
+    @NonNull
+    @Override
+    public Bundle getMetaData() {
+        return metaData == null ? Bundle.EMPTY : metaData;
+    }
+
+    @NonNull
+    @Override
+    public List<ParsedIntentInfo> getIntents() {
+        return new ArrayList<>(intents);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -149,7 +166,7 @@
         dest.writeInt(this.getDescriptionRes());
         dest.writeInt(this.getFlags());
         sForInternedString.parcel(this.packageName, dest, flags);
-        dest.writeTypedList(this.getIntents());
+        dest.writeTypedList(this.intents);
         dest.writeBundle(this.metaData);
         dest.writeMap(this.mProperties);
     }
@@ -234,16 +251,6 @@
     }
 
     @DataClass.Generated.Member
-    public @NonNull List<ParsedIntentInfo> getIntents() {
-        return intents;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable Bundle getMetaData() {
-        return metaData;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull Map<String,Property> getProperties() {
         return mProperties;
     }
@@ -297,10 +304,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627680195484L,
+            time = 1641414207885L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java",
-            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic  void addIntent(android.content.pm.parsing.component.ParsedIntentInfo)\npublic  void addProperty(android.content.pm.PackageManager.Property)\npublic  android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\n  void addIntent(android.content.pm.parsing.component.ParsedIntentInfoImpl)\n  void addProperty(android.content.pm.PackageManager.Property)\npublic  android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
index e208854..c6b9b1a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
@@ -21,9 +21,6 @@
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -34,6 +31,9 @@
 import android.util.TypedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 /** @hide */
 class ParsedComponentUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
index a0eae8c..c325d8d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedInstrumentation extends ParsedComponent {
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
index c8baa9e..23ebdc8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
@@ -33,7 +33,7 @@
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedInstrumentationImpl extends ParsedComponentImpl implements
-        ParsedInstrumentation {
+        ParsedInstrumentation, Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -165,10 +165,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627595809880L,
+            time = 1641431951575L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
index 51e1428..c63a689 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -19,7 +19,6 @@
 import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -27,12 +26,15 @@
 import android.content.res.XmlResourceParser;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 
-/** @hide */
+/**
+ * @hide
+ */
 public class ParsedInstrumentationUtils {
 
     @NonNull
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
index 57b486a..a7f7b00 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java
@@ -19,10 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
-import android.os.Parcelable;
 
 /** @hide **/
-public interface ParsedIntentInfo extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedIntentInfo {
 
     boolean isHasDefault();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
index 1c816da..5b6375d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
@@ -32,7 +32,7 @@
         genBuilder = false, genConstructor = false)
 @DataClass.Suppress({"setIntentFilter"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedIntentInfoImpl implements ParsedIntentInfo {
+public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable {
 
     private boolean mHasDefault;
     private int mLabelRes;
@@ -169,10 +169,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627691925408L,
+            time = 1641431952314L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java",
-            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
+            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
index 1e6f630..4f0a504 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -21,9 +21,6 @@
 import android.annotation.NonNull;
 import android.content.Intent;
 import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -34,6 +31,9 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -49,7 +49,7 @@
     public static final boolean DEBUG = false;
 
     @NonNull
-    public static ParseResult<ParsedIntentInfo> parseIntentInfo(String className,
+    public static ParseResult<ParsedIntentInfoImpl> parseIntentInfo(String className,
             ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
             boolean allowAutoVerify, ParseInput input)
             throws XmlPullParserException, IOException {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
index 8c1d6c8..b926d53 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java
@@ -16,17 +16,20 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedMainComponent extends ParsedComponent {
 
-    @Nullable
+    @NonNull
     String[] getAttributionTags();
 
     /**
      * A main component's name is a class name. This makes code slightly more readable.
      */
+    @NonNull
     String getClassName();
 
     boolean isDirectBootAware();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
index 9b57f48..a2f2f93 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,10 +28,15 @@
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
-/** @hide */
+import libcore.util.EmptyArray;
+
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent {
+public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent,
+        Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -71,6 +77,12 @@
         return getName();
     }
 
+    @NonNull
+    @Override
+    public String[] getAttributionTags() {
+        return attributionTags == null ? EmptyArray.STRING : attributionTags;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -178,51 +190,46 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable String[] getAttributionTags() {
-        return attributionTags;
-    }
-
-    @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
+    public @NonNull ParsedMainComponentImpl setDirectBootAware( boolean value) {
         directBootAware = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setEnabled( boolean value) {
+    public @NonNull ParsedMainComponentImpl setEnabled( boolean value) {
         enabled = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setExported( boolean value) {
+    public @NonNull ParsedMainComponentImpl setExported( boolean value) {
         exported = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setOrder( int value) {
+    public @NonNull ParsedMainComponentImpl setOrder( int value) {
         order = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setSplitName(@android.annotation.NonNull String value) {
+    public @NonNull ParsedMainComponentImpl setSplitName(@NonNull String value) {
         splitName = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedMainComponentImpl setAttributionTags(@android.annotation.NonNull String... value) {
+    public @NonNull ParsedMainComponentImpl setAttributionTags(@NonNull String... value) {
         attributionTags = value;
         return this;
     }
 
     @DataClass.Generated(
-            time = 1627324857874L,
+            time = 1641414540422L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
index 2a3e653..f52ad13 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
@@ -21,8 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Configuration;
@@ -33,6 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -110,13 +110,13 @@
         return input.success(component);
     }
 
-    static ParseResult<ParsedIntentInfo> parseIntentFilter(
+    static ParseResult<ParsedIntentInfoImpl> parseIntentFilter(
             ParsedMainComponent mainComponent,
             ParsingPackage pkg, Resources resources, XmlResourceParser parser,
             boolean visibleToEphemeral, boolean allowGlobs, boolean allowAutoVerify,
             boolean allowImplicitEphemeralVisibility, boolean failOnNoActions,
             ParseInput input) throws IOException, XmlPullParserException {
-        ParseResult<ParsedIntentInfo> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
+        ParseResult<ParsedIntentInfoImpl> intentResult = ParsedIntentInfoUtils.parseIntentInfo(
                 mainComponent.getName(), pkg, resources, parser, allowGlobs,
                 allowAutoVerify, input);
         if (intentResult.isError()) {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
index 4a6d2c3..dc5347a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java
@@ -16,11 +16,13 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import java.util.Set;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedPermission extends ParsedComponent {
 
     @Nullable
@@ -29,7 +31,7 @@
     @Nullable
     String getGroup();
 
-    @Nullable
+    @NonNull
     Set<String> getKnownCerts();
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
index 73b5ffa..64f4fbd 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java
@@ -17,15 +17,16 @@
 package com.android.server.pm.pkg.component;
 
 /** @hide */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedPermissionGroup extends ParsedComponent {
 
-    int getBackgroundRequestDetailResourceId();
+    int getBackgroundRequestDetailRes();
 
-    int getBackgroundRequestResourceId();
+    int getBackgroundRequestRes();
 
     int getPriority();
 
-    int getRequestDetailResourceId();
+    int getRequestDetailRes();
 
     int getRequestRes();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
index f47fb75..59075de 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
@@ -16,21 +16,25 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 
-/** @hide */
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
         genAidl = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements
-        ParsedPermissionGroup {
+        ParsedPermissionGroup, Parcelable {
 
-    private int requestDetailResourceId;
-    private int backgroundRequestResourceId;
-    private int backgroundRequestDetailResourceId;
+    private int requestDetailRes;
+    private int backgroundRequestRes;
+    private int backgroundRequestDetailRes;
     private int requestRes;
     private int priority;
 
@@ -43,6 +47,25 @@
     public ParsedPermissionGroupImpl() {
     }
 
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(requestDetailRes);
+        dest.writeInt(backgroundRequestRes);
+        dest.writeInt(backgroundRequestDetailRes);
+        dest.writeInt(requestRes);
+        dest.writeInt(priority);
+    }
+
+    protected ParsedPermissionGroupImpl(@NonNull Parcel in) {
+        super(in);
+        this.requestDetailRes = in.readInt();
+        this.backgroundRequestRes = in.readInt();
+        this.backgroundRequestDetailRes = in.readInt();
+        this.requestRes = in.readInt();
+        this.priority = in.readInt();
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -51,7 +74,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -60,14 +83,14 @@
 
     @DataClass.Generated.Member
     public ParsedPermissionGroupImpl(
-            int requestDetailResourceId,
-            int backgroundRequestResourceId,
-            int backgroundRequestDetailResourceId,
+            int requestDetailRes,
+            int backgroundRequestRes,
+            int backgroundRequestDetailRes,
             int requestRes,
             int priority) {
-        this.requestDetailResourceId = requestDetailResourceId;
-        this.backgroundRequestResourceId = backgroundRequestResourceId;
-        this.backgroundRequestDetailResourceId = backgroundRequestDetailResourceId;
+        this.requestDetailRes = requestDetailRes;
+        this.backgroundRequestRes = backgroundRequestRes;
+        this.backgroundRequestDetailRes = backgroundRequestDetailRes;
         this.requestRes = requestRes;
         this.priority = priority;
 
@@ -75,18 +98,18 @@
     }
 
     @DataClass.Generated.Member
-    public int getRequestDetailResourceId() {
-        return requestDetailResourceId;
+    public int getRequestDetailRes() {
+        return requestDetailRes;
     }
 
     @DataClass.Generated.Member
-    public int getBackgroundRequestResourceId() {
-        return backgroundRequestResourceId;
+    public int getBackgroundRequestRes() {
+        return backgroundRequestRes;
     }
 
     @DataClass.Generated.Member
-    public int getBackgroundRequestDetailResourceId() {
-        return backgroundRequestDetailResourceId;
+    public int getBackgroundRequestDetailRes() {
+        return backgroundRequestDetailRes;
     }
 
     @DataClass.Generated.Member
@@ -100,97 +123,58 @@
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestDetailResourceId( int value) {
-        requestDetailResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setRequestDetailRes( int value) {
+        requestDetailRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestResourceId( int value) {
-        backgroundRequestResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setBackgroundRequestRes( int value) {
+        backgroundRequestRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailResourceId( int value) {
-        backgroundRequestDetailResourceId = value;
+    public @NonNull ParsedPermissionGroupImpl setBackgroundRequestDetailRes( int value) {
+        backgroundRequestDetailRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
+    public @NonNull ParsedPermissionGroupImpl setRequestRes( int value) {
         requestRes = value;
         return this;
     }
 
     @DataClass.Generated.Member
-    public @android.annotation.NonNull ParsedPermissionGroupImpl setPriority( int value) {
+    public @NonNull ParsedPermissionGroupImpl setPriority( int value) {
         priority = value;
         return this;
     }
 
     @Override
     @DataClass.Generated.Member
-    public void writeToParcel(@android.annotation.NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        super.writeToParcel(dest, flags);
-
-        dest.writeInt(requestDetailResourceId);
-        dest.writeInt(backgroundRequestResourceId);
-        dest.writeInt(backgroundRequestDetailResourceId);
-        dest.writeInt(requestRes);
-        dest.writeInt(priority);
-    }
-
-    @Override
-    @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    protected ParsedPermissionGroupImpl(@android.annotation.NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        super(in);
-
-        int _requestDetailResourceId = in.readInt();
-        int _backgroundRequestResourceId = in.readInt();
-        int _backgroundRequestDetailResourceId = in.readInt();
-        int _requestRes = in.readInt();
-        int _priority = in.readInt();
-
-        this.requestDetailResourceId = _requestDetailResourceId;
-        this.backgroundRequestResourceId = _backgroundRequestResourceId;
-        this.backgroundRequestDetailResourceId = _backgroundRequestDetailResourceId;
-        this.requestRes = _requestRes;
-        this.priority = _priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @android.annotation.NonNull android.os.Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
-            = new android.os.Parcelable.Creator<ParsedPermissionGroupImpl>() {
+    public static final @NonNull Parcelable.Creator<ParsedPermissionGroupImpl> CREATOR
+            = new Parcelable.Creator<ParsedPermissionGroupImpl>() {
         @Override
         public ParsedPermissionGroupImpl[] newArray(int size) {
             return new ParsedPermissionGroupImpl[size];
         }
 
         @Override
-        public ParsedPermissionGroupImpl createFromParcel(@android.annotation.NonNull Parcel in) {
+        public ParsedPermissionGroupImpl createFromParcel(@NonNull Parcel in) {
             return new ParsedPermissionGroupImpl(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1627602253988L,
+            time = 1642132854167L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java",
-            inputSignatures = "private  int requestDetailResourceId\nprivate  int backgroundRequestResourceId\nprivate  int backgroundRequestDetailResourceId\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\nclass ParsedPermissionGroupImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermissionGroup]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java",
+            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
index 98007ff..70986c3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
@@ -29,13 +29,17 @@
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 import com.android.internal.util.Parcelling.BuiltIn.ForStringSet;
 
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Set;
 
-/** @hide */
+/**
+ * @hide
+ */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission {
+public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedPermission,
+        Parcelable {
 
     private static ForStringSet sForStringSet = Parcelling.Cache.getOrCreate(ForStringSet.class);
 
@@ -56,14 +60,8 @@
     public ParsedPermissionImpl() {
     }
 
-    public ParsedPermissionImpl(ParsedPermission other) {
-        super(other);
-        this.backgroundPermission = other.getBackgroundPermission();
-        this.group = other.getGroup();
-        this.requestRes = other.getRequestRes();
-        this.protectionLevel = other.getProtectionLevel();
-        this.tree = other.isTree();
-        this.parsedPermissionGroup = other.getParsedPermissionGroup();
+    public ParsedPermissionGroup getParsedPermissionGroup() {
+        return parsedPermissionGroup;
     }
 
     public ParsedPermissionImpl setGroup(String group) {
@@ -84,6 +82,12 @@
         }
     }
 
+    @NonNull
+    @Override
+    public Set<String> getKnownCerts() {
+        return knownCerts == null ? Collections.emptySet() : knownCerts;
+    }
+
     public String toString() {
         return "Permission{"
                 + Integer.toHexString(System.identityHashCode(this))
@@ -103,7 +107,7 @@
         dest.writeInt(this.requestRes);
         dest.writeInt(this.protectionLevel);
         dest.writeBoolean(this.tree);
-        dest.writeParcelable(this.parsedPermissionGroup, flags);
+        dest.writeParcelable((ParsedPermissionGroupImpl) this.parsedPermissionGroup, flags);
         sForStringSet.parcel(knownCerts, dest, flags);
     }
 
@@ -115,7 +119,7 @@
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
         this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(),
-                ParsedPermissionGroup.class);
+                ParsedPermissionGroupImpl.class);
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
@@ -155,7 +159,7 @@
             int requestRes,
             int protectionLevel,
             boolean tree,
-            @Nullable ParsedPermissionGroup parsedPermissionGroup,
+            @Nullable ParsedPermissionGroupImpl parsedPermissionGroup,
             @Nullable Set<String> knownCerts) {
         this.backgroundPermission = backgroundPermission;
         this.group = group;
@@ -194,16 +198,6 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable ParsedPermissionGroup getParsedPermissionGroup() {
-        return parsedPermissionGroup;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable Set<String> getKnownCerts() {
-        return knownCerts;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull ParsedPermissionImpl setBackgroundPermission(@NonNull String value) {
         backgroundPermission = value;
         return this;
@@ -240,10 +234,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627598236506L,
+            time = 1641414649731L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java",
-            inputSignatures = "private static  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private static  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroupImpl parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic  android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
index 8562fdf..f2e2f4f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
@@ -20,8 +20,6 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PermissionInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -31,13 +29,17 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.List;
 
-/** @hide */
+/**
+ * @hide
+ */
 public class ParsedPermissionUtils {
 
     private static final String TAG = ParsingUtils.TAG;
@@ -107,7 +109,7 @@
                         permission.setKnownCert(knownCert);
                     }
                 }
-                if (permission.getKnownCerts() == null) {
+                if (permission.getKnownCerts().isEmpty()) {
                     Slog.w(TAG, packageName + " defines a knownSigner permission but"
                             + " the provided knownCerts resource is null");
                 }
@@ -228,9 +230,9 @@
             }
 
             // @formatter:off
-            permissionGroup.setRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
-                    .setBackgroundRequestResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
-                    .setBackgroundRequestDetailResourceId(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
+            permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
+                    .setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
+                    .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
                     .setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
                     .setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
                     .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
index ff391ff..608d08e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java
@@ -17,14 +17,15 @@
 package com.android.server.pm.pkg.component;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.content.pm.ApplicationInfo;
-import android.os.Parcelable;
 import android.util.ArrayMap;
 
 import java.util.Set;
 
 /** @hide */
-public interface ParsedProcess extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedProcess {
 
     @NonNull
     Set<String> getDeniedPermissions();
@@ -44,6 +45,7 @@
      * It's a map, because in shared processes, different packages can have different application
      * classes.
      */
+    @SuppressLint("ConcreteCollection")
     @NonNull
     ArrayMap<String, String> getAppClassNamesByPackage();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
index 96560c7..6d52f65 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
@@ -36,7 +36,7 @@
 @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false,
         genBuilder = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProcessImpl implements ParsedProcess {
+public class ParsedProcessImpl implements ParsedProcess, Parcelable {
 
     @NonNull
     private String name;
@@ -305,10 +305,10 @@
     };
 
     @DataClass.Generated(
-            time = 1639076603310L,
+            time = 1641431953775L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
index d03f153..4f4c2d5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
@@ -18,8 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -31,6 +29,8 @@
 import com.android.internal.R;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
index 8cc6fc7..ba85e07 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java
@@ -16,11 +16,15 @@
 
 package com.android.server.pm.pkg.component;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PathPermission;
 import android.os.PatternMatcher;
 
+import java.util.List;
+
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedProvider extends ParsedMainComponent {
 
     @Nullable
@@ -30,13 +34,17 @@
 
     boolean isMultiProcess();
 
-    @Nullable PathPermission[] getPathPermissions();
+    @NonNull
+    List<PathPermission> getPathPermissions();
 
-    @Nullable String getReadPermission();
+    @Nullable
+    String getReadPermission();
 
-    @Nullable PatternMatcher[] getUriPermissionPatterns();
+    @NonNull
+    List<PatternMatcher> getUriPermissionPatterns();
 
-    @Nullable String getWritePermission();
+    @Nullable
+    String getWritePermission();
 
     boolean isForceUriPermissions();
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
index e04fc86..e77ecb3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
@@ -28,13 +28,22 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
-/** @hide **/
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @hide
+ **/
 @DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
+@DataClass.Suppress({"setUriPermissionPatterns", "setPathPermissions"})
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider {
+public class ParsedProviderImpl extends ParsedMainComponentImpl implements ParsedProvider,
+        Parcelable {
 
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -50,10 +59,10 @@
     private boolean forceUriPermissions;
     private boolean multiProcess;
     private int initOrder;
-    @Nullable
-    private PatternMatcher[] uriPermissionPatterns;
-    @Nullable
-    private PathPermission[] pathPermissions;
+    @NonNull
+    private List<PatternMatcher> uriPermissionPatterns = Collections.emptyList();
+    @NonNull
+    private List<PathPermission> pathPermissions = Collections.emptyList();
 
     public ParsedProviderImpl(ParsedProvider other) {
         super(other);
@@ -66,8 +75,8 @@
         this.forceUriPermissions = other.isForceUriPermissions();
         this.multiProcess = other.isMultiProcess();
         this.initOrder = other.getInitOrder();
-        this.uriPermissionPatterns = other.getUriPermissionPatterns();
-        this.pathPermissions = other.getPathPermissions();
+        this.uriPermissionPatterns = new ArrayList<>(other.getUriPermissionPatterns());
+        this.pathPermissions = new ArrayList<>(other.getPathPermissions());
     }
 
     public ParsedProviderImpl setReadPermission(String readPermission) {
@@ -84,6 +93,18 @@
         return this;
     }
 
+    @NonNull
+    public ParsedProviderImpl addUriPermissionPattern(@NonNull PatternMatcher value) {
+        uriPermissionPatterns = CollectionUtils.add(uriPermissionPatterns, value);
+        return this;
+    }
+
+    @NonNull
+    public ParsedProviderImpl addPathPermission(@NonNull PathPermission value) {
+        pathPermissions = CollectionUtils.add(pathPermissions, value);
+        return this;
+    }
+
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("Provider{");
@@ -110,8 +131,8 @@
         dest.writeBoolean(this.forceUriPermissions);
         dest.writeBoolean(this.multiProcess);
         dest.writeInt(this.initOrder);
-        dest.writeTypedArray(this.uriPermissionPatterns, flags);
-        dest.writeTypedArray(this.pathPermissions, flags);
+        dest.writeTypedList(this.uriPermissionPatterns, flags);
+        dest.writeTypedList(this.pathPermissions, flags);
     }
 
     public ParsedProviderImpl() {
@@ -127,8 +148,8 @@
         this.forceUriPermissions = in.readBoolean();
         this.multiProcess = in.readBoolean();
         this.initOrder = in.readInt();
-        this.uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
-        this.pathPermissions = in.createTypedArray(PathPermission.CREATOR);
+        this.uriPermissionPatterns = in.createTypedArrayList(PatternMatcher.CREATOR);
+        this.pathPermissions = in.createTypedArrayList(PathPermission.CREATOR);
     }
 
     @NonNull
@@ -153,7 +174,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -170,8 +191,8 @@
             boolean forceUriPermissions,
             boolean multiProcess,
             int initOrder,
-            @Nullable PatternMatcher[] uriPermissionPatterns,
-            @Nullable PathPermission[] pathPermissions) {
+            @NonNull List<PatternMatcher> uriPermissionPatterns,
+            @NonNull List<PathPermission> pathPermissions) {
         this.authority = authority;
         this.syncable = syncable;
         this.readPermission = readPermission;
@@ -181,7 +202,11 @@
         this.multiProcess = multiProcess;
         this.initOrder = initOrder;
         this.uriPermissionPatterns = uriPermissionPatterns;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, uriPermissionPatterns);
         this.pathPermissions = pathPermissions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, pathPermissions);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -227,12 +252,12 @@
     }
 
     @DataClass.Generated.Member
-    public @Nullable PatternMatcher[] getUriPermissionPatterns() {
+    public @NonNull List<PatternMatcher> getUriPermissionPatterns() {
         return uriPermissionPatterns;
     }
 
     @DataClass.Generated.Member
-    public @Nullable PathPermission[] getPathPermissions() {
+    public @NonNull List<PathPermission> getPathPermissions() {
         return pathPermissions;
     }
 
@@ -272,23 +297,11 @@
         return this;
     }
 
-    @DataClass.Generated.Member
-    public @NonNull ParsedProviderImpl setUriPermissionPatterns(@NonNull PatternMatcher... value) {
-        uriPermissionPatterns = value;
-        return this;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull ParsedProviderImpl setPathPermissions(@NonNull PathPermission... value) {
-        pathPermissions = value;
-        return this;
-    }
-
     @DataClass.Generated(
-            time = 1627590522169L,
+            time = 1642560323360L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.Nullable android.os.PatternMatcher[] uriPermissionPatterns\nprivate @android.annotation.Nullable android.content.pm.PathPermission[] pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedProviderImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedProvider]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
index 9d3129b..4cb4dd0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
@@ -16,16 +16,14 @@
 
 package com.android.server.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.Resources;
@@ -36,6 +34,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -173,14 +173,14 @@
             final ParseResult result;
             switch (name) {
                 case "intent-filter":
-                    ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+                    ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
                             .parseIntentFilter(provider, pkg, res, parser, visibleToEphemeral,
                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
                                     false /*allowImplicitEphemeralVisibility*/,
                                     false /*failOnNoActions*/, input);
                     result = intentResult;
                     if (intentResult.isSuccess()) {
-                        ParsedIntentInfo intent = intentResult.getResult();
+                        ParsedIntentInfoImpl intent = intentResult.getResult();
                         IntentFilter intentFilter = intent.getIntentFilter();
                         provider.setOrder(Math.max(intentFilter.getOrder(), provider.getOrder()));
                         provider.addIntent(intent);
@@ -253,16 +253,7 @@
             }
 
             if (pa != null) {
-                if (provider.getUriPermissionPatterns() == null) {
-                    provider.setUriPermissionPatterns(new PatternMatcher[1]);
-                    provider.getUriPermissionPatterns()[0] = pa;
-                } else {
-                    final int N = provider.getUriPermissionPatterns().length;
-                    PatternMatcher[] newp = new PatternMatcher[N + 1];
-                    System.arraycopy(provider.getUriPermissionPatterns(), 0, newp, 0, N);
-                    newp[N] = pa;
-                    provider.setUriPermissionPatterns(newp);
-                }
+                provider.addUriPermissionPattern(pa);
                 provider.setGrantUriPermissions(true);
             } else {
                 if (RIGID_PARSER) {
@@ -357,16 +348,7 @@
             }
 
             if (pa != null) {
-                if (provider.getPathPermissions() == null) {
-                    provider.setPathPermissions(new PathPermission[1]);
-                    provider.getPathPermissions()[0] = pa;
-                } else {
-                    final int N = provider.getPathPermissions().length;
-                    PathPermission[] newp = new PathPermission[N + 1];
-                    System.arraycopy(provider.getPathPermissions(), 0, newp, 0, N);
-                    newp[N] = pa;
-                    provider.setPathPermissions(newp);
-                }
+                provider.addPathPermission(pa);
             } else {
                 if (RIGID_PARSER) {
                     return input.error(
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
index 11696be..5fc251c 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 /** @hide **/
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface ParsedService extends ParsedMainComponent {
 
     int getForegroundServiceType();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
index 0171c49..c00e01a 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
@@ -32,7 +32,8 @@
 /** @hide **/
 @DataClass(genSetters = true, genGetters = true, genParcelable = false, genBuilder = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService {
+public class ParsedServiceImpl extends ParsedMainComponentImpl implements ParsedService,
+        Parcelable {
 
     private int foregroundServiceType;
     @Nullable
@@ -138,10 +139,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627592563052L,
+            time = 1641431954479L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java",
-            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index 6fe9411..1940eb5 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -23,8 +23,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ServiceInfo;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
 import android.content.pm.parsing.result.ParseResult;
@@ -34,6 +32,8 @@
 import android.os.Build;
 
 import com.android.internal.R;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
+import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -133,14 +133,14 @@
             final ParseResult parseResult;
             switch (parser.getName()) {
                 case "intent-filter":
-                    ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
+                    ParseResult<ParsedIntentInfoImpl> intentResult = ParsedMainComponentUtils
                             .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
                                     true /*allowGlobs*/, false /*allowAutoVerify*/,
                                     false /*allowImplicitEphemeralVisibility*/,
                                     false /*failOnNoActions*/, input);
                     parseResult = intentResult;
                     if (intentResult.isSuccess()) {
-                        ParsedIntentInfo intent = intentResult.getResult();
+                        ParsedIntentInfoImpl intent = intentResult.getResult();
                         IntentFilter intentFilter = intent.getIntentFilter();
                         service.setOrder(Math.max(intentFilter.getOrder(), service.getOrder()));
                         service.addIntent(intent);
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
index 8e3401e..e17d1c4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.pm.PackageInfo;
-import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -30,7 +29,8 @@
  *
  * @hide
  */
-public interface ParsedUsesPermission extends Parcelable {
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ParsedUsesPermission {
 
     /**
      * Strong assertion by a developer that they will never use this permission to derive the
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
index 70d6f24..9b89373 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
@@ -33,7 +33,7 @@
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = true,
         genAidl = false)
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class ParsedUsesPermissionImpl implements ParsedUsesPermission {
+public class ParsedUsesPermissionImpl implements ParsedUsesPermission, Parcelable {
 
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
     @NonNull
@@ -157,10 +157,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627674645598L,
+            time = 1641431955242L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
index 2d6c616..b92b845 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
@@ -41,6 +42,7 @@
 import android.content.pm.SigningInfo;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.Environment;
+import android.os.PatternMatcher;
 import android.os.UserHandle;
 
 import com.android.internal.util.ArrayUtils;
@@ -651,8 +653,8 @@
         pi.forceUriPermissions = p.isForceUriPermissions();
         pi.multiprocess = p.isMultiProcess();
         pi.initOrder = p.getInitOrder();
-        pi.uriPermissionPatterns = p.getUriPermissionPatterns();
-        pi.pathPermissions = p.getPathPermissions();
+        pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+        pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
         if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
             pi.uriPermissionPatterns = null;
         }
@@ -690,9 +692,11 @@
         ii.sourceDir = pkg.getBaseApkPath();
         ii.publicSourceDir = pkg.getBaseApkPath();
         ii.splitNames = pkg.getSplitNames();
-        ii.splitSourceDirs = pkg.getSplitCodePaths();
-        ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
-        ii.splitDependencies = pkg.getSplitDependencies();
+        ii.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+        ii.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+                ? null : pkg.getSplitCodePaths();
+        ii.splitDependencies = pkg.getSplitDependencies().size() == 0
+                ? null : pkg.getSplitDependencies();
 
         if (assignUserFields) {
             assignUserFields(pkg, ii, userId);
@@ -734,9 +738,9 @@
         if (pg == null) return null;
 
         PermissionGroupInfo pgi = new PermissionGroupInfo(
-                pg.getRequestDetailResourceId(),
-                pg.getBackgroundRequestResourceId(),
-                pg.getBackgroundRequestDetailResourceId()
+                pg.getRequestDetailRes(),
+                pg.getBackgroundRequestRes(),
+                pg.getBackgroundRequestDetailRes()
         );
 
         assignSharedFieldsForPackageItemInfo(pgi, pg);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
index 1f21938..84422cd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java
@@ -24,6 +24,7 @@
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -81,11 +82,8 @@
 import com.android.server.pm.pkg.component.ParsedServiceImpl;
 import com.android.server.pm.pkg.component.ParsedUsesPermission;
 import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageHidden;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+
+import libcore.util.EmptyArray;
 
 import java.security.PublicKey;
 import java.util.Collections;
@@ -107,6 +105,7 @@
  */
 public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, Parcelable {
 
+    private static final SparseArray<int[]> EMPTY_INT_ARRAY_SPARSE_ARRAY = new SparseArray<>();
     public static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
     public static ForInternedString sForInternedString = Parcelling.Cache.getOrCreate(
             ForInternedString.class);
@@ -660,6 +659,7 @@
         return this;
     }
 
+    @SuppressLint("MissingSuperCall")
     @CallSuper
     @Override
     public Object hideAsParsed() {
@@ -1125,7 +1125,8 @@
 //        appInfo.sharedLibraryInfos
 //        appInfo.showUserIcon
         appInfo.splitClassLoaderNames = splitClassLoaderNames;
-        appInfo.splitDependencies = splitDependencies;
+        appInfo.splitDependencies = (splitDependencies == null || splitDependencies.size() == 0)
+                ? null : splitDependencies;
         appInfo.splitNames = splitNames;
         appInfo.storageUuid = mStorageUuid;
         appInfo.targetSandboxVersion = targetSandboxVersion;
@@ -1144,8 +1145,8 @@
         appInfo.setBaseResourcePath(mBaseApkPath);
         appInfo.setCodePath(mPath);
         appInfo.setResourcePath(mPath);
-        appInfo.setSplitCodePaths(splitCodePaths);
-        appInfo.setSplitResourcePaths(splitCodePaths);
+        appInfo.setSplitCodePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
+        appInfo.setSplitResourcePaths(ArrayUtils.size(splitCodePaths) == 0 ? null : splitCodePaths);
         appInfo.setVersionCode(mLongVersionCode);
         appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
         appInfo.setLocaleConfigRes(mLocaleConfigRes);
@@ -1257,20 +1258,20 @@
         dest.writeStringList(this.originalPackages);
         sForInternedStringList.parcel(this.adoptPermissions, dest, flags);
         sForInternedStringList.parcel(this.requestedPermissions, dest, flags);
-        dest.writeTypedList(this.usesPermissions);
+        ParsingUtils.writeParcelableList(dest, this.usesPermissions);
         sForInternedStringList.parcel(this.implicitPermissions, dest, flags);
         sForStringSet.parcel(this.upgradeKeySets, dest, flags);
         ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
         sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
-        dest.writeTypedList(this.activities);
-        dest.writeTypedList(this.apexSystemServices);
-        dest.writeTypedList(this.receivers);
-        dest.writeTypedList(this.services);
-        dest.writeTypedList(this.providers);
-        dest.writeTypedList(this.attributions);
-        dest.writeTypedList(this.permissions);
-        dest.writeTypedList(this.permissionGroups);
-        dest.writeTypedList(this.instrumentations);
+        ParsingUtils.writeParcelableList(dest, this.activities);
+        ParsingUtils.writeParcelableList(dest, this.apexSystemServices);
+        ParsingUtils.writeParcelableList(dest, this.receivers);
+        ParsingUtils.writeParcelableList(dest, this.services);
+        ParsingUtils.writeParcelableList(dest, this.providers);
+        ParsingUtils.writeParcelableList(dest, this.attributions);
+        ParsingUtils.writeParcelableList(dest, this.permissions);
+        ParsingUtils.writeParcelableList(dest, this.permissionGroups);
+        ParsingUtils.writeParcelableList(dest, this.instrumentations);
         sForIntentInfoPairs.parcel(this.preferredActivityFilters, dest, flags);
         dest.writeMap(this.processes);
         dest.writeBundle(this.metaData);
@@ -1906,22 +1907,22 @@
         return queriesProviders;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitClassLoaderNames() {
-        return splitClassLoaderNames;
+        return splitClassLoaderNames == null ? EmptyArray.STRING : splitClassLoaderNames;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitCodePaths() {
-        return splitCodePaths;
+        return splitCodePaths == null ? EmptyArray.STRING : splitCodePaths;
     }
 
     @Nullable
     @Override
     public SparseArray<int[]> getSplitDependencies() {
-        return splitDependencies;
+        return splitDependencies == null ? EMPTY_INT_ARRAY_SPARSE_ARRAY : splitDependencies;
     }
 
     @Nullable
@@ -1930,16 +1931,16 @@
         return splitFlags;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public String[] getSplitNames() {
-        return splitNames;
+        return splitNames == null ? EmptyArray.STRING : splitNames;
     }
 
-    @Nullable
+    @NonNull
     @Override
     public int[] getSplitRevisionCodes() {
-        return splitRevisionCodes;
+        return splitRevisionCodes == null ? EmptyArray.INT : splitRevisionCodes;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index bf7c55f..e8f03ff 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -65,6 +65,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -101,6 +102,7 @@
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedInstrumentationUtils;
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.server.pm.pkg.component.ParsedIntentInfoUtils;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.component.ParsedPermission;
@@ -230,6 +232,8 @@
      * of required system property within the overlay tag.
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
+    public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
+
     public static final int PARSE_CHATTY = 1 << 31;
 
     @IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -241,6 +245,7 @@
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
             PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
+            PARSE_FRAMEWORK_RES_SPLITS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ParseFlags {}
@@ -289,7 +294,7 @@
                 return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
             }
         });
-        result = parser.parsePackage(input, file, parseFlags);
+        result = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
         if (result.isError()) {
             return input.error(result);
         }
@@ -343,26 +348,44 @@
      * not check whether {@code packageFile} has changed since the last parse, it's up to callers to
      * do so.
      */
-    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
-        if (packageFile.isDirectory()) {
-            return parseClusterPackage(input, packageFile, flags);
+    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
+            List<File> frameworkSplits) {
+        if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
+                && frameworkSplits.size() > 0
+                && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
+            return parseClusterPackage(input, packageFile, frameworkSplits, flags);
+        } else if (packageFile.isDirectory()) {
+            return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
         } else {
             return parseMonolithicPackage(input, packageFile, flags);
         }
     }
 
     /**
-     * Parse all APKs contained in the given directory, treating them as a single package. This also
-     * performs validity checking, such as requiring identical package name and version codes, a
-     * single base APK, and unique split names.
+     * Parse all APKs contained in the given directory, treating them as a
+     * single package. This also performs validity checking, such as requiring
+     * identical package name and version codes, a single base APK, and unique
+     * split names.
+     * <p>
+     * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
+     * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
+     * even if they are in different folders. Note that this code path may have other behaviour
+     * differences.
      * <p>
      * Note that this <em>does not</em> perform signature verification; that must be done separately
      * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}.
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
-            int flags) {
+            List<File> frameworkSplits, int flags) {
+        // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
+        // pass the flags for framework splits
+        int liteParseFlags = 0;
+        if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
+            liteParseFlags = flags;
+        }
         final ParseResult<PackageLite> liteResult =
-                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
+                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
+                        liteParseFlags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
@@ -1673,7 +1696,7 @@
                 continue;
             }
             if (parser.getName().equals("intent")) {
-                ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(
+                ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
                         null /*className*/, pkg, res, parser, true /*allowGlobs*/,
                         true /*allowAutoVerify*/, input);
                 if (result.isError()) {
@@ -2679,9 +2702,8 @@
             // as it would have already been set when we processed the activity. We wait to
             // process the meta data here since this method is called at the end of processing
             // the application and all meta data is guaranteed.
-            final float activityAspectRatio = activity.getMetaData() != null
-                    ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
-                    : maxAspectRatio;
+            final float activityAspectRatio = activity.getMetaData()
+                    .getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
 
             ComponentMutateUtils.setMaxAspectRatio(activity, activity.getResizeMode(),
                     activityAspectRatio);
@@ -2716,9 +2738,8 @@
         int activitiesSize = activities.size();
         for (int index = 0; index < activitiesSize; index++) {
             ParsedActivity activity = activities.get(index);
-            if (supportsSizeChanges || (activity.getMetaData() != null
-                    && activity.getMetaData().getBoolean(
-                            METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+            if (supportsSizeChanges || activity.getMetaData()
+                    .getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false)) {
                 ComponentMutateUtils.setSupportsSizeChanges(activity, true);
             }
         }
@@ -2991,9 +3012,12 @@
             }
 
             signingDetails = result.getResult();
-
+            final File frameworkRes = new File(Environment.getRootDirectory(),
+                    "framework/framework-res.apk");
+            boolean isFrameworkResSplit = frameworkRes.getAbsolutePath()
+                    .equals(pkg.getBaseApkPath());
             String[] splitCodePaths = pkg.getSplitCodePaths();
-            if (!ArrayUtils.isEmpty(splitCodePaths)) {
+            if (!ArrayUtils.isEmpty(splitCodePaths) && !isFrameworkResSplit) {
                 for (int i = 0; i < splitCodePaths.length; i++) {
                     result = getSigningDetails(
                             input,
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
index 95fec36..9430e98 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
@@ -20,8 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import com.android.server.pm.pkg.component.ParsedIntentInfo;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.res.XmlResourceParser;
@@ -32,6 +30,8 @@
 
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -84,7 +84,8 @@
     }
 
     /**
-     * Use with {@link Parcel#writeTypedList(List)}
+     * Use with {@link Parcel#writeTypedList(List)} or
+     * {@link #writeInterfaceAsImplList(Parcel, List)}
      *
      * @see Parcel#createTypedArrayList(Parcelable.Creator)
      */
@@ -103,6 +104,29 @@
         return list;
     }
 
+    /**
+     * Use with {@link #createTypedInterfaceList(Parcel, Parcelable.Creator)}.
+     *
+     * Writes a list that can be cast as Parcelable types at runtime.
+     * TODO: Remove with ImmutableList multi-casting support
+     *
+     * @see Parcel#writeTypedList(List)
+     */
+    @NonNull
+    public static void writeParcelableList(@NonNull Parcel parcel, List<?> list) {
+        if (list == null) {
+            parcel.writeInt(-1);
+            return;
+        }
+        int size = list.size();
+        int index = 0;
+        parcel.writeInt(size);
+        while (index < size) {
+            parcel.writeTypedObject((Parcelable) list.get(index), 0);
+            index++;
+        }
+    }
+
     public static class StringPairListParceler implements
             Parcelling<List<Pair<String, ParsedIntentInfo>>> {
 
@@ -120,7 +144,7 @@
             for (int index = 0; index < size; index++) {
                 Pair<String, ParsedIntentInfo> pair = item.get(index);
                 dest.writeString(pair.first);
-                dest.writeParcelable(pair.second, parcelFlags);
+                dest.writeParcelable((Parcelable) pair.second, parcelFlags);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
index a323e20..2ef90ac 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java
@@ -324,13 +324,13 @@
      * @see ApplicationInfo#splitSourceDirs
      * @see ApplicationInfo#getSplitCodePaths
      */
-    @Nullable
+    @NonNull
     String[] getSplitCodePaths();
 
     /**
      * @see ApplicationInfo#splitDependencies
      */
-    @Nullable
+    @NonNull
     SparseArray<int[]> getSplitDependencies();
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
index 2bc4ee7..78ce6f1 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java
@@ -249,12 +249,13 @@
      * @see ApplicationInfo#splitNames
      * @see PackageInfo#splitNames
      */
-    @Nullable
+    @NonNull
     String[] getSplitNames();
 
     /**
      * @see PackageInfo#splitRevisionCodes
      */
+    @NonNull
     int[] getSplitRevisionCodes();
 
     /**
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index d0b50d27..d6c89f7 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -60,7 +60,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState;
@@ -1751,7 +1751,7 @@
         int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
                 userId, debugObject);
         if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
-            PackageUserState pkgUserState = pkgSetting.getUserStateOrDefault(userId);
+            PackageUserStateInternal pkgUserState = pkgSetting.getUserStateOrDefault(userId);
             if (!pkgUserState.isInstalled()) {
                 return APPROVAL_LEVEL_NOT_INSTALLED;
             }
@@ -1783,7 +1783,7 @@
             return APPROVAL_LEVEL_UNDECLARED;
         }
 
-        final PackageUserState pkgUserState = pkgSetting.getUserStates().get(userId);
+        final PackageUserStateInternal pkgUserState = pkgSetting.getUserStates().get(userId);
         if (pkgUserState == null) {
             if (DEBUG_APPROVAL) {
                 debugApproval(packageName, debugObject, userId, false,
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
index 154f9a4..7754944 100644
--- a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -27,12 +27,11 @@
  *
  * @see DeviceStateProviderImpl
  */
-public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
-    private final Context mContext;
+public final class DeviceStatePolicyImpl extends DeviceStatePolicy {
     private final DeviceStateProvider mProvider;
 
-    public DeviceStatePolicyImpl(Context context) {
-        mContext = context;
+    public DeviceStatePolicyImpl(@NonNull Context context) {
+        super(context);
         mProvider = DeviceStateProviderImpl.create(mContext);
     }
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b7ca4de..e180032 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -18,9 +18,6 @@
 
 import static android.app.AppOpsManager.OP_FLAG_SELF;
 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.usage.NetworkStatsManager.FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_FORCE;
-import static android.app.usage.NetworkStatsManager.FLAG_POLL_ON_OPEN;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -82,6 +79,7 @@
 import android.app.RuntimeAppOpAccessMessage;
 import android.app.StatsManager;
 import android.app.StatsManager.PullAtomMetadata;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.UidTraffic;
@@ -100,8 +98,6 @@
 import android.media.MediaDrm;
 import android.media.UnsupportedSchemeException;
 import android.net.ConnectivityManager;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
 import android.net.Network;
 import android.net.NetworkRequest;
 import android.net.NetworkStats;
@@ -197,6 +193,7 @@
 import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.net.module.util.NetworkStatsUtils;
 import com.android.role.RoleManagerLocal;
 import com.android.server.BinderCallsStatsService;
 import com.android.server.LocalManagerRegistry;
@@ -244,7 +241,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -340,6 +337,7 @@
     private WifiManager mWifiManager;
     private TelephonyManager mTelephony;
     private SubscriptionManager mSubscriptionManager;
+    private NetworkStatsManager mNetworkStatsManager;
 
     @GuardedBy("mKernelWakelockLock")
     private KernelWakelockReader mKernelWakelockReader;
@@ -788,7 +786,7 @@
                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
         mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
-
+        mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
         // Initialize DiskIO
         mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
 
@@ -981,32 +979,6 @@
         registerOemManagedBytesTransfer();
     }
 
-    /**
-     * Return the {@code INetworkStatsSession} object that holds the necessary properties needed
-     * for the subsequent queries to {@link com.android.server.net.NetworkStatsService}. Or
-     * null if the service or binder cannot be obtained. Calling this method will trigger poll
-     * in NetworkStatsService with once per 15 seconds rate-limit, unless {@code bypassRateLimit}
-     * is set to true. This is needed in {@link #getUidNetworkStatsSnapshotForTemplate}, where
-     * bypassing the limit is necessary for perfd to supply realtime stats to developers looking at
-     * the network usage of their app.
-     */
-    @Nullable
-    private INetworkStatsSession getNetworkStatsSession(boolean bypassRateLimit) {
-        final INetworkStatsService networkStatsService =
-                INetworkStatsService.Stub.asInterface(
-                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
-        if (networkStatsService == null) return null;
-
-        try {
-            return networkStatsService.openSessionForUsageStats(
-                    FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN | (bypassRateLimit ? FLAG_POLL_FORCE
-                            : FLAG_POLL_ON_OPEN), mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Cannot get NetworkStats session", e);
-            return null;
-        }
-    }
-
     private IThermalService getIThermalService() {
         synchronized (mThermalLock) {
             if (mThermalService == null) {
@@ -1137,8 +1109,8 @@
             case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
                 final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
                 if (stats != null) {
-                    ret.add(new NetworkStatsExt(stats.groupedByUid(), new int[] {TRANSPORT_WIFI},
-                                    /*slicedByFgbg=*/false));
+                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
+                            new int[] {TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
                 }
                 break;
             }
@@ -1154,7 +1126,7 @@
                 final NetworkStats stats =
                         getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
                 if (stats != null) {
-                    ret.add(new NetworkStatsExt(stats.groupedByUid(),
+                    ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
                                     new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
                 }
                 break;
@@ -1245,23 +1217,19 @@
 
     private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
             @NonNull NetworkStatsExt statsExt) {
-        int size = statsExt.stats.size();
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
-        for (int j = 0; j < size; j++) {
-            statsExt.stats.getValues(j, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             StatsEvent statsEvent;
-
             if (statsExt.slicedByFgbg) {
                 // MobileBytesTransferByFgBg atom or WifiBytesTransferByFgBg atom.
                 statsEvent = FrameworkStatsLog.buildStatsEvent(
-                        atomTag, entry.uid,
-                        (entry.set > 0), entry.rxBytes, entry.rxPackets, entry.txBytes,
-                        entry.txPackets);
+                        atomTag, entry.getUid(),
+                        (entry.getSet() > 0), entry.getRxBytes(), entry.getRxPackets(),
+                        entry.getTxBytes(), entry.getTxPackets());
             } else {
                 // MobileBytesTransfer atom or WifiBytesTransfer atom.
                 statsEvent = FrameworkStatsLog.buildStatsEvent(
-                        atomTag, entry.uid, entry.rxBytes,
-                        entry.rxPackets, entry.txBytes, entry.txPackets);
+                        atomTag, entry.getUid(), entry.getRxBytes(),
+                        entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets());
             }
             ret.add(statsEvent);
         }
@@ -1269,13 +1237,12 @@
 
     private void addBytesTransferByTagAndMeteredAtoms(@NonNull NetworkStatsExt statsExt,
             @NonNull List<StatsEvent> pulledData) {
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
-        for (int i = 0; i < statsExt.stats.size(); i++) {
-            statsExt.stats.getValues(i, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.uid,
-                    entry.metered == NetworkStats.METERED_YES, entry.tag, entry.rxBytes,
-                    entry.rxPackets, entry.txBytes, entry.txPackets));
+                    FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.getUid(),
+                    entry.getMetered() == NetworkStats.METERED_YES, entry.getTag(),
+                    entry.getRxBytes(), entry.getRxPackets(), entry.getTxBytes(),
+                    entry.getTxPackets()));
         }
     }
 
@@ -1290,12 +1257,11 @@
         // Report NR connected in 5G non-standalone mode, or if the RAT type is NR to begin with.
         final boolean isNR = is5GNsa || statsExt.ratType == TelephonyManager.NETWORK_TYPE_NR;
 
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
-        for (int i = 0; i < statsExt.stats.size(); i++) {
-            statsExt.stats.getValues(i, entry);
+        for (NetworkStats.Entry entry : statsExt.stats) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER, entry.set, entry.rxBytes,
-                    entry.rxPackets, entry.txBytes, entry.txPackets,
+                    FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER,
+                    entry.getSet(), entry.getRxBytes(), entry.getRxPackets(),
+                    entry.getTxBytes(), entry.getTxPackets(),
                     is5GNsa ? TelephonyManager.NETWORK_TYPE_LTE : statsExt.ratType,
                     // Fill information about subscription, these cannot be null since invalid data
                     // would be filtered when adding into subInfo list.
@@ -1309,15 +1275,13 @@
 
     private void addOemDataUsageBytesTransferAtoms(@NonNull NetworkStatsExt statsExt,
             @NonNull List<StatsEvent> pulledData) {
-        final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
         final int oemManaged = statsExt.oemManaged;
         for (final int transport : statsExt.transports) {
-            for (int i = 0; i < statsExt.stats.size(); i++) {
-                statsExt.stats.getValues(i, entry);
+            for (NetworkStats.Entry entry : statsExt.stats) {
                 pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                        FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.uid, (entry.set > 0),
-                        oemManaged, transport, entry.rxBytes, entry.rxPackets, entry.txBytes,
-                        entry.txPackets));
+                        FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER, entry.getUid(),
+                        (entry.getSet() > 0), oemManaged, transport, entry.getRxBytes(),
+                        entry.getRxPackets(), entry.getTxBytes(), entry.getTxPackets()));
             }
         }
     }
@@ -1385,22 +1349,32 @@
         final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
         final long bucketDuration = Settings.Global.getLong(mContext.getContentResolver(),
                 NETSTATS_UID_BUCKET_DURATION, NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS);
-        try {
-            // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
-            //  history when query in every second in order to show realtime statistics. However,
-            //  this is not a good long-term solution since NetworkStatsService will make frequent
-            //  I/O and also block main thread when polling.
-            //  Consider making perfd queries NetworkStatsService directly.
-            final NetworkStats stats = getNetworkStatsSession(template.getMatchRule()
-                    == NetworkTemplate.MATCH_WIFI_WILDCARD).getSummaryForAllUid(template,
-                    currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
-                    currentTimeInMillis, includeTags);
-            return stats;
-        } catch (RemoteException | NullPointerException e) {
-            Slog.e(TAG, "Pulling netstats for template=" + template + " and includeTags="
-                    + includeTags  + " causes error", e);
+
+        // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
+        //  history when query in every second in order to show realtime statistics. However,
+        //  this is not a good long-term solution since NetworkStatsService will make frequent
+        //  I/O and also block main thread when polling.
+        //  Consider making perfd queries NetworkStatsService directly.
+        if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+            mNetworkStatsManager.forceUpdate();
         }
-        return null;
+
+        final android.app.usage.NetworkStats queryNonTaggedStats =
+                mNetworkStatsManager.querySummary(
+                template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+                currentTimeInMillis);
+
+        final NetworkStats nonTaggedStats =
+                NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats);
+        if (!includeTags) return nonTaggedStats;
+
+        final android.app.usage.NetworkStats quaryTaggedStats =
+                mNetworkStatsManager.queryTaggedSummary(template,
+                currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
+                currentTimeInMillis);
+        final NetworkStats taggedStats =
+                NetworkStatsUtils.fromPublicNetworkStats(quaryTaggedStats);
+        return nonTaggedStats.add(taggedStats);
     }
 
     @NonNull private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
@@ -1424,27 +1398,51 @@
         return ret;
     }
 
+    @NonNull private NetworkStats sliceNetworkStatsByUid(@NonNull NetworkStats stats) {
+        return sliceNetworkStats(stats,
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
+                });
+    }
+
     @NonNull private NetworkStats sliceNetworkStatsByFgbg(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.set = oldEntry.set;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL,
+                                entry.getSet(), NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
     @NonNull private NetworkStats sliceNetworkStatsByUidAndFgbg(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.uid = oldEntry.uid;
-                    newEntry.set = oldEntry.set;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                entry.getSet(), NetworkStats.TAG_NONE,
+                                NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
     @NonNull private NetworkStats sliceNetworkStatsByUidTagAndMetered(@NonNull NetworkStats stats) {
         return sliceNetworkStats(stats,
-                (newEntry, oldEntry) -> {
-                    newEntry.uid = oldEntry.uid;
-                    newEntry.tag = oldEntry.tag;
-                    newEntry.metered = oldEntry.metered;
+                (entry) -> {
+                        return new NetworkStats.Entry(null /* IFACE_ALL */, entry.getUid(),
+                                NetworkStats.SET_ALL, entry.getTag(),
+                                entry.getMetered(), NetworkStats.ROAMING_ALL,
+                                NetworkStats.DEFAULT_NETWORK_ALL,
+                                entry.getRxBytes(), entry.getRxPackets(),
+                                entry.getTxBytes(), entry.getTxPackets(), 0);
                 });
     }
 
@@ -1454,46 +1452,31 @@
      *
      * This function iterates through each NetworkStats.Entry, sets its dimensions equal to the
      * default state (with the presumption that we don't want to slice on anything), and then
-     * applies the slicer lambda to allow users to control which dimensions to slice on. This is
-     * adapted from groupedByUid within NetworkStats.java
+     * applies the slicer lambda to allow users to control which dimensions to slice on.
      *
-     * @param slicer An operation taking into two parameters, new NetworkStats.Entry and old
-     *               NetworkStats.Entry, that should be used to copy state from the old to the new.
+     * @param slicer An operation taking one parameter, NetworkStats.Entry, that should be used to
+     *               get the state from entry to replace the default value.
      *               This is useful for slicing by particular dimensions. For example, if we wished
      *               to slice by uid and tag, we could write the following lambda:
-     *                  (new, old) -> {
-     *                          new.uid = old.uid;
-     *                          new.tag = old.tag;
+     *                  (entry) -> {
+     *                   return new NetworkStats.Entry(null, entry.getUid(),
+     *                           NetworkStats.SET_ALL, entry.getTag(),
+     *                           NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+     *                           NetworkStats.DEFAULT_NETWORK_ALL,
+     *                           entry.getRxBytes(), entry.getRxPackets(),
+     *                           entry.getTxBytes(), entry.getTxPackets(), 0);
      *                  }
-     *               If no slicer is provided, the data is not sliced by any dimensions.
      * @return new NeworkStats object appropriately sliced
      */
     @NonNull private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
-            @Nullable BiConsumer<NetworkStats.Entry, NetworkStats.Entry> slicer) {
-        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        entry.uid = NetworkStats.UID_ALL;
-        entry.iface = NetworkStats.IFACE_ALL;
-        entry.set = NetworkStats.SET_ALL;
-        entry.tag = NetworkStats.TAG_NONE;
-        entry.metered = NetworkStats.METERED_ALL;
-        entry.roaming = NetworkStats.ROAMING_ALL;
-        entry.defaultNetwork = NetworkStats.DEFAULT_NETWORK_ALL;
-
-        final NetworkStats.Entry recycle = new NetworkStats.Entry(); // used for retrieving values
-        for (int i = 0; i < stats.size(); i++) {
-            stats.getValues(i, recycle);
+            @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
+        NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+        NetworkStats.Entry entry = new NetworkStats.Entry();
+        for (NetworkStats.Entry e : stats) {
             if (slicer != null) {
-                slicer.accept(entry, recycle);
+                entry = slicer.apply(e);
             }
-
-            entry.rxBytes = recycle.rxBytes;
-            entry.rxPackets = recycle.rxPackets;
-            entry.txBytes = recycle.txBytes;
-            entry.txPackets = recycle.txPackets;
-            // Operations purposefully omitted since we don't use them for statsd.
-            ret.combineValues(entry);
+            ret = ret.addEntry(entry);
         }
         return ret;
     }
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 8189e74..160f4f9 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -26,8 +26,8 @@
 import java.util.List;
 
 /**
- * Adapter that clips frequency values to {@link VibratorInfo#getFrequencyRangeHz()} and
- * amplitude values to respective {@link VibratorInfo#getMaxAmplitude}.
+ * Adapter that clips frequency values to the ones specified by the
+ * {@link VibratorInfo.FrequencyProfile}.
  *
  * <p>Devices with no frequency control will collapse all frequencies to the resonant frequency and
  * leave amplitudes unchanged.
@@ -69,20 +69,20 @@
     }
 
     private float clampFrequency(VibratorInfo info, float frequencyHz) {
-        Range<Float> frequencyRangeHz = info.getFrequencyRangeHz();
+        Range<Float> frequencyRangeHz = info.getFrequencyProfile().getFrequencyRangeHz();
         if (frequencyHz == 0 || frequencyRangeHz == null)  {
-            return info.getResonantFrequency();
+            return info.getResonantFrequencyHz();
         }
         return frequencyRangeHz.clamp(frequencyHz);
     }
 
     private float clampAmplitude(VibratorInfo info, float frequencyHz, float amplitude) {
-        Range<Float> frequencyRangeHz = info.getFrequencyRangeHz();
-        if (frequencyRangeHz == null) {
-            // No frequency range was specified, leave amplitude unchanged, the frequency will be
-            // clamped to the device's resonant frequency.
+        VibratorInfo.FrequencyProfile mapping = info.getFrequencyProfile();
+        if (mapping.isEmpty()) {
+            // No frequency mapping was specified so leave amplitude unchanged.
+            // The frequency will be clamped to the device's resonant frequency.
             return amplitude;
         }
-        return MathUtils.min(amplitude, info.getMaxAmplitude(frequencyHz));
+        return MathUtils.min(amplitude, mapping.getMaxAmplitude(frequencyHz));
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index c592a70..c943bb2 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -94,6 +94,6 @@
     }
 
     private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
-        return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
+        return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index 5ace389..86fc642 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -148,6 +148,6 @@
     }
 
     private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
-        return frequencyHz == 0 ? info.getResonantFrequency() : frequencyHz;
+        return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 76434c7..49d6a34 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -95,6 +95,7 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
+import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.COLOR_MODE_DEFAULT;
@@ -635,7 +636,7 @@
     private boolean mOccludesParent;
 
     // The input dispatching timeout for this application token in milliseconds.
-    long mInputDispatchingTimeoutMillis;
+    long mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
     private boolean mShowWhenLocked;
     private boolean mInheritShownWhenLocked;
@@ -1443,7 +1444,6 @@
         if (oldParent == null && newParent != null) {
             // First time we are adding the activity to the system.
             mVoiceInteraction = newTask.voiceSession != null;
-            mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
 
             // TODO(b/36505427): Maybe this call should be moved inside
             // updateOverrideConfiguration()
@@ -1995,6 +1995,7 @@
             task.setRootProcess(proc);
         }
         proc.addActivityIfNeeded(this);
+        mInputDispatchingTimeoutMillis = getInputDispatchingTimeoutMillisLocked(this);
 
         // Update the associated task fragment after setting the process, since it's required for
         // filtering to only report activities that belong to the same process.
@@ -3578,6 +3579,7 @@
             app.removeActivity(this, false /* keepAssociation */);
         }
         app = null;
+        mInputDispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
     }
 
     void makeFinishingLocked() {
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
similarity index 97%
rename from services/core/java/com/android/server/wm/FadeRotationAnimationController.java
rename to services/core/java/com/android/server/wm/AsyncRotationController.java
index 2cefd99..0990f1f 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -35,7 +35,7 @@
  * both fixed rotation and normal rotation to hide some non-activity windows. The caller should show
  * the windows until they are drawn with the new rotation.
  */
-public class FadeRotationAnimationController extends FadeAnimationController {
+public class AsyncRotationController extends FadeAnimationController {
 
     /** The map of window token to its animation leash. */
     private final ArrayMap<WindowToken, SurfaceControl> mTargetWindowTokens = new ArrayMap<>();
@@ -70,7 +70,7 @@
     private final int mOriginalRotation;
     private final boolean mHasScreenRotationAnimation;
 
-    public FadeRotationAnimationController(DisplayContent displayContent) {
+    public AsyncRotationController(DisplayContent displayContent) {
         super(displayContent);
         mService = displayContent.mWmService;
         mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
@@ -81,7 +81,7 @@
         mIsStartTransactionCommitted = !mIsChangeTransition;
         mTimeoutRunnable = displayContent.inTransition() ? () -> {
             synchronized (mService.mGlobalLock) {
-                displayContent.finishFadeRotationAnimationIfPossible();
+                displayContent.finishAsyncRotationIfPossible();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
         } : null;
@@ -277,7 +277,7 @@
                 mIsStartTransactionCommitted = true;
                 if (mPendingShowTokens == null) return;
                 for (int i = mPendingShowTokens.size() - 1; i >= 0; i--) {
-                    mDisplayContent.finishFadeRotationAnimation(mPendingShowTokens.get(i));
+                    mDisplayContent.finishAsyncRotation(mPendingShowTokens.get(i));
                 }
                 mPendingShowTokens = null;
             }
@@ -298,7 +298,7 @@
                 // Only fade in the drawn windows. If the remaining windows are drawn later,
                 // show(WindowToken) will be called to fade in them.
                 if (token.getChildAt(j).isDrawFinishedLw()) {
-                    mDisplayContent.finishFadeRotationAnimation(token);
+                    mDisplayContent.finishAsyncRotation(token);
                     break;
                 }
             }
@@ -320,7 +320,7 @@
             }
             return true;
         }
-        mDisplayContent.finishFadeRotationAnimation(w.mToken);
+        mDisplayContent.finishAsyncRotation(w.mToken);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index bb4519c..5c1ddd9 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -64,6 +64,11 @@
         void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
     }
 
+    interface SyncEngineListener {
+        /** Called when there is no more active sync set. */
+        void onSyncEngineFree();
+    }
+
     /**
      * Holds state associated with a single synchronous set of operations.
      */
@@ -137,6 +142,9 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             mActiveSyncs.remove(mSyncId);
             mWm.mH.removeCallbacks(mOnTimeout);
+            if (mSyncEngineListener != null && mActiveSyncs.size() == 0) {
+                mSyncEngineListener.onSyncEngineFree();
+            }
         }
 
         private void setReady(boolean ready) {
@@ -175,22 +183,54 @@
     private final WindowManagerService mWm;
     private int mNextSyncId = 0;
     private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
+    private SyncEngineListener mSyncEngineListener;
 
     BLASTSyncEngine(WindowManagerService wms) {
         mWm = wms;
     }
 
+    /** Sets listener listening to whether the sync engine is free. */
+    void setSyncEngineListener(SyncEngineListener listener) {
+        mSyncEngineListener = listener;
+    }
+
+    /**
+     * Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
+     * before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
+     */
+    SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
+        return new SyncGroup(listener, mNextSyncId++, name);
+    }
+
     int startSyncSet(TransactionReadyListener listener) {
         return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION, "");
     }
 
     int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
-        final int id = mNextSyncId++;
-        final SyncGroup s = new SyncGroup(listener, id, name);
-        mActiveSyncs.put(id, s);
-        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
+        final SyncGroup s = prepareSyncSet(listener, name);
+        startSyncSet(s, timeoutMs);
+        return s.mSyncId;
+    }
+
+    void startSyncSet(SyncGroup s) {
+        startSyncSet(s, WindowState.BLAST_TIMEOUT_DURATION);
+    }
+
+    void startSyncSet(SyncGroup s, long timeoutMs) {
+        if (mActiveSyncs.size() != 0) {
+            // We currently only support one sync at a time, so start a new SyncGroup when there is
+            // another may cause issue.
+            ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
+                    "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
+        }
+        mActiveSyncs.put(s.mSyncId, s);
+        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
+                s.mSyncId, s.mListener);
         scheduleTimeout(s, timeoutMs);
-        return id;
+    }
+
+    boolean hasActiveSync() {
+        return mActiveSyncs.size() != 0;
     }
 
     @VisibleForTesting
@@ -199,11 +239,11 @@
     }
 
     void addToSyncSet(int id, WindowContainer wc) {
-        mActiveSyncs.get(id).addToSync(wc);
+        getSyncGroup(id).addToSync(wc);
     }
 
     void setReady(int id, boolean ready) {
-        mActiveSyncs.get(id).setReady(ready);
+        getSyncGroup(id).setReady(ready);
     }
 
     void setReady(int id) {
@@ -211,14 +251,22 @@
     }
 
     boolean isReady(int id) {
-        return mActiveSyncs.get(id).mReady;
+        return getSyncGroup(id).mReady;
     }
 
     /**
      * Aborts the sync (ie. it doesn't wait for ready or anything to finish)
      */
     void abort(int id) {
-        mActiveSyncs.get(id).finishNow();
+        getSyncGroup(id).finishNow();
+    }
+
+    private SyncGroup getSyncGroup(int id) {
+        final SyncGroup syncGroup = mActiveSyncs.get(id);
+        if (syncGroup == null) {
+            throw new IllegalStateException("SyncGroup is not started yet id=" + id);
+        }
+        return syncGroup;
     }
 
     void onSurfacePlacement() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e449dde..5facc0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -123,6 +123,7 @@
 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;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
 import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
@@ -193,13 +194,13 @@
 import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.RotationUtils;
+import android.util.Size;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayCutout;
-import android.view.DisplayCutout.CutoutPathParserInfo;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
@@ -238,7 +239,6 @@
 import com.android.internal.util.function.pooled.PooledPredicate;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.wm.utils.DisplayRotationUtil;
 import com.android.server.wm.utils.RotationCache;
 import com.android.server.wm.utils.WmDisplayCutout;
 
@@ -323,6 +323,11 @@
      */
     private Rect mLastMirroredDisplayAreaBounds = null;
 
+    /**
+     * The default per Display minimal size of tasks. Calculated at construction.
+     */
+    int mMinSizeOfResizeableTaskDp = -1;
+
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed. We use a subclass of
@@ -547,7 +552,7 @@
 
     /** The delay to avoid toggling the animation quickly. */
     private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250;
-    private FadeRotationAnimationController mFadeRotationAnimationController;
+    private AsyncRotationController mAsyncRotationController;
 
     final FixedRotationTransitionListener mFixedRotationTransitionListener =
             new FixedRotationTransitionListener();
@@ -578,8 +583,6 @@
     /** Caches the value whether told display manager that we have content. */
     private boolean mLastHasContent;
 
-    private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
-
     /**
      * The input method window for this display.
      */
@@ -1098,7 +1101,7 @@
 
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
-
+        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
 
         mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
@@ -1554,6 +1557,19 @@
         return config;
     }
 
+    private int getMinimalTaskSizeDp() {
+        final Context displayConfigurationContext =
+                mAtmService.mContext.createConfigurationContext(getConfiguration());
+        final float minimalSize =
+                displayConfigurationContext.getResources().getDimension(
+                                com.android.internal.R.dimen.default_minimal_size_resizable_task);
+        if (Double.compare(mDisplayMetrics.density, 0.0) == 0) {
+            throw new IllegalArgumentException("Display with ID=" + getDisplayId() + "has invalid "
+                + "DisplayMetrics.density= 0.0");
+        }
+        return (int) (minimalSize / mDisplayMetrics.density);
+    }
+
     private boolean updateOrientation(boolean forceUpdate) {
         final int orientation = getOrientation();
         // The last orientation source is valid only after getOrientation.
@@ -1712,8 +1728,8 @@
     }
 
     @VisibleForTesting
-    @Nullable FadeRotationAnimationController getFadeRotationAnimationController() {
-        return mFadeRotationAnimationController;
+    @Nullable AsyncRotationController getAsyncRotationController() {
+        return mAsyncRotationController;
     }
 
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
@@ -1723,13 +1739,13 @@
     void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
         if (mFixedRotationLaunchingApp == null && r != null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
-            startFadeRotationAnimation(
+            startAsyncRotation(
                     // Delay the hide animation to avoid blinking by clicking navigation bar that
                     // may toggle fixed rotation in a short time.
                     r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */);
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
-            finishFadeRotationAnimationIfPossible();
+            finishAsyncRotationIfPossible();
         }
         mFixedRotationLaunchingApp = r;
     }
@@ -1842,9 +1858,9 @@
         return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
     }
 
-    private void startFadeRotationAnimationIfNeeded() {
+    private void startAsyncRotationIfNeeded() {
         if (isRotationChanging()) {
-            startFadeRotationAnimation(false /* shouldDebounce */);
+            startAsyncRotation(false /* shouldDebounce */);
         }
     }
 
@@ -1853,12 +1869,12 @@
      *
      * @return {@code true} if the animation is executed right now.
      */
-    private boolean startFadeRotationAnimation(boolean shouldDebounce) {
+    private boolean startAsyncRotation(boolean shouldDebounce) {
         if (shouldDebounce) {
             mWmService.mH.postDelayed(() -> {
                 synchronized (mWmService.mGlobalLock) {
                     if (mFixedRotationLaunchingApp != null
-                            && startFadeRotationAnimation(false /* shouldDebounce */)) {
+                            && startAsyncRotation(false /* shouldDebounce */)) {
                         // Apply the transaction so the animation leash can take effect immediately.
                         getPendingTransaction().apply();
                     }
@@ -1866,28 +1882,28 @@
             }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS);
             return false;
         }
-        if (mFadeRotationAnimationController == null) {
-            mFadeRotationAnimationController = new FadeRotationAnimationController(this);
-            mFadeRotationAnimationController.hide();
+        if (mAsyncRotationController == null) {
+            mAsyncRotationController = new AsyncRotationController(this);
+            mAsyncRotationController.hide();
             return true;
         }
         return false;
     }
 
     /** Re-show the previously hidden windows if all seamless rotated windows are done. */
-    void finishFadeRotationAnimationIfPossible() {
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+    void finishAsyncRotationIfPossible() {
+        final AsyncRotationController controller = mAsyncRotationController;
         if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
             controller.show();
-            mFadeRotationAnimationController = null;
+            mAsyncRotationController = null;
         }
     }
 
     /** Shows the given window which may be hidden for screen rotation. */
-    void finishFadeRotationAnimation(WindowToken windowToken) {
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+    void finishAsyncRotation(WindowToken windowToken) {
+        final AsyncRotationController controller = mAsyncRotationController;
         if (controller != null && controller.show(windowToken)) {
-            mFadeRotationAnimationController = null;
+            mAsyncRotationController = null;
         }
     }
 
@@ -1897,7 +1913,7 @@
             // The window should look no different before and after rotation.
             return false;
         }
-        final FadeRotationAnimationController controller = mFadeRotationAnimationController;
+        final AsyncRotationController controller = mAsyncRotationController;
         return controller == null || !controller.isHandledToken(w.mToken);
     }
 
@@ -2077,20 +2093,12 @@
             return WmDisplayCutout.computeSafeInsets(
                     cutout, displayWidth, displayHeight);
         }
-        final Insets waterfallInsets =
-                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
+        final DisplayCutout rotatedCutout =
+                cutout.getRotated(displayWidth, displayHeight, ROTATION_0, rotation);
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        final Rect[] newBounds = sRotationUtil.getRotatedBounds(
-                cutout.getBoundingRectsAll(),
-                rotation, displayWidth, displayHeight);
-        final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
-        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
-                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
-                info.getCutoutSpec(), rotation, info.getScale());
-        return WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
+        return new WmDisplayCutout(rotatedCutout, new Size(
                 rotated ? displayHeight : displayWidth,
-                rotated ? displayWidth : displayHeight);
+                rotated ? displayWidth : displayHeight));
     }
 
     private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
@@ -2711,6 +2719,7 @@
             //                    layout.
             mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChanged */);
         }
+        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -3206,7 +3215,7 @@
         // Hide the windows which are not significant in rotation animation. So that the windows
         // don't need to block the unfreeze time.
         if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
-            startFadeRotationAnimationIfNeeded();
+            startAsyncRotationIfNeeded();
         }
     }
 
@@ -3228,7 +3237,7 @@
             }
             if (!controller.isCollecting(this)) {
                 controller.collect(this);
-                startFadeRotationAnimationIfNeeded();
+                startAsyncRotationIfNeeded();
             }
             return;
         }
@@ -3240,7 +3249,7 @@
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                 controller.mTransitionMetricsReporter.associate(t,
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
-                startFadeRotationAnimation(false /* shouldDebounce */);
+                startAsyncRotation(false /* shouldDebounce */);
             }
             t.setKnownConfigChanges(this, changes);
         }
@@ -3272,6 +3281,7 @@
             screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
         }
         mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
+        proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
         if (mTransitionController.isShellTransitionsEnabled()) {
             mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
         } else {
@@ -3349,6 +3359,7 @@
         pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
         pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
         pw.print("dpi");
+        pw.print(" mMinSizeOfResizeableTaskDp="); pw.print(mMinSizeOfResizeableTaskDp);
         if (mInitialDisplayWidth != mBaseDisplayWidth
                 || mInitialDisplayHeight != mBaseDisplayHeight
                 || mInitialDisplayDensity != mBaseDisplayDensity) {
@@ -3983,8 +3994,8 @@
             mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken);
             // Hide the window until the rotation is done to avoid intermediate artifacts if the
             // parent surface of IME container is changed.
-            if (mFadeRotationAnimationController != null) {
-                mFadeRotationAnimationController.hideImmediately(mInputMethodWindow.mToken);
+            if (mAsyncRotationController != null) {
+                mAsyncRotationController.hideImmediately(mInputMethodWindow.mToken);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ab1e349..1888554 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -305,10 +305,10 @@
     private WindowState mRoundedCornerWindow;
 
     /**
-     * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for
-     * the conditions of being candidate window.
+     * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
+     * which appearance.
      */
-    private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>();
+    private final ArrayList<AppearanceRegion> mStatusBarAppearanceRegionList = new ArrayList<>();
 
     /**
      * Windows to determine opacity and background of translucent status bar. The window needs to be
@@ -323,7 +323,7 @@
     private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
 
-    /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */
+    /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */
     private final Rect mStatusBarColorCheckedBounds = new Rect();
 
     /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */
@@ -336,6 +336,7 @@
     private long mPendingPanicGestureUptime;
 
     private static final Rect sTmpRect = new Rect();
+    private static final Rect sTmpRect2 = new Rect();
     private static final Rect sTmpLastParentFrame = new Rect();
     private static final Rect sTmpDisplayCutoutSafe = new Rect();
     private static final Rect sTmpDisplayFrame = new Rect();
@@ -1526,7 +1527,7 @@
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
         mNavBarBackgroundWindow = null;
-        mStatusBarColorWindows.clear();
+        mStatusBarAppearanceRegionList.clear();
         mStatusBarBackgroundWindows.clear();
         mStatusBarColorCheckedBounds.setEmpty();
         mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1606,7 +1607,9 @@
                 mStatusBarBackgroundWindows.add(win);
                 mStatusBarBackgroundCheckedBounds.union(sTmpRect);
                 if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) {
-                    mStatusBarColorWindows.add(win);
+                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(
+                            win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+                            new Rect(win.getFrame())));
                     mStatusBarColorCheckedBounds.union(sTmpRect);
                 }
             }
@@ -1626,13 +1629,10 @@
                 }
             }
         } else if (win.isDimming()) {
-            // For dimming window whose host bounds is overlapping with system bars, it can be
-            // used to determine colors but not opacity of system bars.
-            if (mStatusBar != null
-                    && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame())
-                    && !mStatusBarColorCheckedBounds.contains(sTmpRect)) {
-                mStatusBarColorWindows.add(win);
-                mStatusBarColorCheckedBounds.union(sTmpRect);
+            if (mStatusBar != null) {
+                addStatusBarAppearanceRegionsForDimmingWindow(
+                        win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+                        mStatusBar.getFrame(), win.getBounds(), win.getFrame());
             }
             if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
@@ -1640,6 +1640,48 @@
         }
     }
 
+    private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
+            Rect winBounds, Rect winFrame) {
+        if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
+            return;
+        }
+        if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
+            return;
+        }
+        if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
+            mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
+            mStatusBarColorCheckedBounds.union(sTmpRect);
+            return;
+        }
+        // A dimming window can divide status bar into different appearance regions (up to 3).
+        // +---------+-------------+---------+
+        // |/////////|             |/////////| <-- Status Bar
+        // +---------+-------------+---------+
+        // |/////////|             |/////////|
+        // |/////////|             |/////////|
+        // |/////////|             |/////////|
+        // |/////////|             |/////////|
+        // |/////////|             |/////////|
+        // +---------+-------------+---------+
+        //      ^           ^           ^
+        //  dim layer     window    dim layer
+        mStatusBarAppearanceRegionList.add(new AppearanceRegion(appearance, new Rect(winFrame)));
+        if (!sTmpRect.equals(sTmpRect2)) {
+            if (sTmpRect.height() == sTmpRect2.height()) {
+                if (sTmpRect.left != sTmpRect2.left) {
+                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
+                            winBounds.left, winBounds.top, sTmpRect2.left, winBounds.bottom)));
+                }
+                if (sTmpRect.right != sTmpRect2.right) {
+                    mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(
+                            sTmpRect2.right, winBounds.top, winBounds.right, winBounds.bottom)));
+                }
+            }
+            // We don't have vertical status bar yet, so we don't handle the other orientation.
+        }
+        mStatusBarColorCheckedBounds.union(sTmpRect);
+    }
+
     /**
      * Called following layout of all windows and after policy has been applied
      * to each window. If in this function you do
@@ -2270,14 +2312,9 @@
         final String focusedApp = win.mAttrs.packageName;
         final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
                 || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
-        final AppearanceRegion[] appearanceRegions =
-                new AppearanceRegion[mStatusBarColorWindows.size()];
-        for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
-            final WindowState windowState = mStatusBarColorWindows.get(i);
-            appearanceRegions[i] = new AppearanceRegion(
-                    getStatusBarAppearance(windowState, windowState),
-                    new Rect(windowState.getFrame()));
-        }
+        final AppearanceRegion[] statusBarAppearanceRegions =
+                new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
+        mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
         if (mLastDisableFlags != disableFlags) {
             mLastDisableFlags = disableFlags;
             final String cause = win.toString();
@@ -2289,7 +2326,7 @@
                 && mRequestedVisibilities.equals(win.getRequestedVisibilities())
                 && Objects.equals(mFocusedApp, focusedApp)
                 && mLastFocusIsFullscreen == isFullscreen
-                && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) {
+                && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) {
             return;
         }
         if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
@@ -2304,20 +2341,12 @@
         mRequestedVisibilities = requestedVisibilities;
         mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
-        mLastStatusBarAppearanceRegions = appearanceRegions;
+        mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
         callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
-                appearance, appearanceRegions, isNavbarColorManagedByIme, behavior,
+                appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
                 requestedVisibilities, focusedApp));
     }
 
-    private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) {
-        final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
-        final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
-        return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard)
-                ? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS)
-                : 0;
-    }
-
     private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
         mHandler.post(() -> {
             StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -2711,11 +2740,10 @@
             pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
             pw.println(mNavBarBackgroundWindow);
         }
-        if (!mStatusBarColorWindows.isEmpty()) {
-            pw.print(prefix); pw.println("mStatusBarColorWindows=");
-            for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
-                final WindowState win = mStatusBarColorWindows.get(i);
-                pw.print(prefixInner); pw.println(win);
+        if (mLastStatusBarAppearanceRegions != null) {
+            pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
+            for (int i = mLastStatusBarAppearanceRegions.length - 1; i >= 0; i--) {
+                pw.print(prefixInner);  pw.println(mLastStatusBarAppearanceRegions[i]);
             }
         }
         if (!mStatusBarBackgroundWindows.isEmpty()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8c8b33f..7387ea3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -638,7 +638,7 @@
         }, true /* traverseTopToBottom */);
         mSeamlessRotationCount = 0;
         mRotatingSeamlessly = false;
-        mDisplayContent.finishFadeRotationAnimationIfPossible();
+        mDisplayContent.finishAsyncRotationIfPossible();
     }
 
     private void prepareSeamlessRotation() {
@@ -729,7 +729,7 @@
                     "Performing post-rotate rotation after seamless rotation");
             // Finish seamless rotation.
             mRotatingSeamlessly = false;
-            mDisplayContent.finishFadeRotationAnimationIfPossible();
+            mDisplayContent.finishAsyncRotationIfPossible();
 
             updateRotationAndSendNewConfigIfChanged();
         }
@@ -1103,6 +1103,21 @@
         return oldRotation != rotation;
     }
 
+
+    /**
+     * Resets whether the screen can be rotated via the accelerometer in all 4 rotations as the
+     * default behavior.
+     *
+     * To be called if there is potential that the value changed. For example if the active display
+     * changed.
+     *
+     * At the moment it is called from
+     * {@link DisplayWindowSettings#applyRotationSettingsToDisplayLocked}.
+     */
+    void resetAllowAllRotations() {
+        mAllowAllRotations = ALLOW_ALL_ROTATIONS_UNDEFINED;
+    }
+
     /**
      * Given an orientation constant, returns the appropriate surface rotation, taking into account
      * sensors, docking mode, rotation lock, and other factors.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 483c799..70c769d 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -297,6 +297,8 @@
         final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
                 ? settings.mIgnoreOrientationRequest : false;
         dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
+
+        dc.getDisplayRotation().resetAllowAllRotations();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a8a9231..21eea94 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -298,9 +298,9 @@
 
     private Point getWindowFrameSurfacePosition() {
         if (mControl != null) {
-            final FadeRotationAnimationController fadeController =
-                    mWin.mDisplayContent.getFadeRotationAnimationController();
-            if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
+            final AsyncRotationController controller =
+                    mWin.mDisplayContent.getAsyncRotationController();
+            if (controller != null && controller.shouldFreezeInsetsPosition(mWin)) {
                 // Use previous position because the fade-out animation runs in old rotation.
                 return mControl.getSurfacePosition();
             }
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index 80f2ab6..0ae119a 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -80,8 +80,8 @@
      * @param show true for fade-in, otherwise for fade-out.
      */
     public void fadeWindowToken(boolean show) {
-        final FadeRotationAnimationController controller =
-                mDisplayContent.getFadeRotationAnimationController();
+        final AsyncRotationController controller =
+                mDisplayContent.getAsyncRotationController();
         final Runnable fadeAnim = () -> fadeWindowToken(show, mNavigationBar.mToken,
                 ANIMATION_TYPE_APP_TRANSITION);
         if (controller == null) {
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 49d30cd..36c092b 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -92,7 +92,7 @@
                 || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
                 && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 && service.getRecentsAnimationController() == null
-                && displayContent.getFadeRotationAnimationController() == null;
+                && displayContent.getAsyncRotationController() == null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index f97a48b..1183094 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -614,7 +614,7 @@
     private void attachNavigationBarToApp() {
         if (!mShouldAttachNavBarToAppDuringTransition
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getFadeRotationAnimationController() != null) {
+                || mDisplayContent.getAsyncRotationController() != null) {
             return;
         }
         boolean shouldTranslateNavBar = false;
@@ -701,7 +701,7 @@
     void animateNavigationBarForAppLaunch(long duration) {
         if (!mShouldAttachNavBarToAppDuringTransition
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getFadeRotationAnimationController() != null
+                || mDisplayContent.getAsyncRotationController() != null
                 || mNavigationBarAttachedToApp
                 || mNavBarAttachedApp == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d031bec..4c72d02b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -73,7 +73,6 @@
 import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
-import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
@@ -111,7 +110,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
@@ -134,7 +132,6 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Slog;
@@ -1220,11 +1217,6 @@
         pw.println(mTopFocusedDisplayId);
     }
 
-    void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
-        pw.print("  mDefaultMinSizeOfResizeableTaskDp=");
-        pw.println(mDefaultMinSizeOfResizeableTaskDp);
-    }
-
     void dumpLayoutNeededDisplayIds(PrintWriter pw) {
         if (!isLayoutNeeded()) {
             return;
@@ -1271,7 +1263,6 @@
         mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
         proto.write(IS_HOME_RECENTS_COMPONENT,
                 mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-        proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
         proto.end(token);
     }
 
@@ -1359,7 +1350,6 @@
                 mDefaultDisplay = displayContent;
             }
         }
-        calculateDefaultMinimalSizeOfResizeableTasks();
 
         final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
         defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
@@ -3477,17 +3467,6 @@
         mService.startLaunchPowerMode(reason);
     }
 
-    // TODO(b/191434136): handle this properly when we add multi-window support on secondary
-    //  display.
-    private void calculateDefaultMinimalSizeOfResizeableTasks() {
-        final Resources res = mService.mContext.getResources();
-        final float minimalSize = res.getDimension(
-                com.android.internal.R.dimen.default_minimal_size_resizable_task);
-        final DisplayMetrics dm = res.getDisplayMetrics();
-
-        mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density);
-    }
-
     /**
      * Dumps the activities matching the given {@param name} in the either the focused root task
      * or all visible root tasks if {@param dumpVisibleRootTasksOnly} is true.
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7acc0c5..9b94f44 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -45,7 +45,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ShortcutServiceInternal;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
@@ -222,17 +221,17 @@
 
     @Override
     public int relayout(IWindow window, WindowManager.LayoutParams attrs,
-            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
+            int requestedWidth, int requestedHeight, int viewFlags, int flags,
             ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
                 + Binder.getCallingPid());
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
         int res = mService.relayoutWindow(this, window, attrs,
-                requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
+                requestedWidth, requestedHeight, viewFlags, flags,
                 outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
-                outActiveControls, outSurfaceSize);
+                outActiveControls);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
                 + Binder.getCallingPid());
@@ -252,6 +251,11 @@
     }
 
     @Override
+    public void clearTouchableRegion(IWindow window) {
+        mService.clearTouchableRegion(this, window);
+    }
+
+    @Override
     public void finishDrawing(IWindow window,
             @Nullable SurfaceControl.Transaction postDrawTransaction) {
         if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 2331dc4..ff9d9f7 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -281,6 +281,8 @@
     // code.
     static final int PERSIST_TASK_VERSION = 1;
 
+    private static final int DEFAULT_MIN_TASK_SIZE_DP = 220;
+
     private float mShadowRadius = 0;
 
     /**
@@ -2052,7 +2054,9 @@
         // so that the user can not render the task fragment too small to manipulate. We don't need
         // to do this for the root pinned task as the bounds are controlled by the system.
         if (!inPinnedWindowingMode()) {
-            final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+            // Use Display specific min sizes when there is one associated with this Task.
+            final int defaultMinSizeDp = mDisplayContent == null
+                    ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
             final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
             final int defaultMinSize = (int) (defaultMinSizeDp * density);
 
@@ -3413,7 +3417,8 @@
         info.isResizeable = isResizeable();
         info.minWidth = mMinWidth;
         info.minHeight = mMinHeight;
-        info.defaultMinSize = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+        info.defaultMinSize = mDisplayContent == null
+                ? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
 
         info.positionInParent = getRelativePosition();
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b13c9a9..ded58f4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -37,7 +37,6 @@
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -72,6 +71,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.animation.Animation;
@@ -101,6 +101,9 @@
     /** The default package for resources */
     private static final String DEFAULT_PACKAGE = "android";
 
+    /** The transition has been created but isn't collecting yet. */
+    private static final int STATE_PENDING = -1;
+
     /** The transition has been created and is collecting, but hasn't formally started. */
     private static final int STATE_COLLECTING = 0;
 
@@ -122,6 +125,7 @@
     private static final int STATE_ABORT = 3;
 
     @IntDef(prefix = { "STATE_" }, value = {
+            STATE_PENDING,
             STATE_COLLECTING,
             STATE_STARTED,
             STATE_PLAYING,
@@ -131,7 +135,7 @@
     @interface TransitionState {}
 
     final @TransitionType int mType;
-    private int mSyncId;
+    private int mSyncId = -1;
     private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
@@ -152,7 +156,7 @@
     final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
 
     /** The final animation targets derived from participants after promotion. */
-    private ArraySet<WindowContainer> mTargets = null;
+    private ArrayList<WindowContainer> mTargets;
 
     /** The main display running this transition. */
     private DisplayContent mTargetDisplay;
@@ -171,7 +175,7 @@
     private IRemoteCallback mClientAnimationStartCallback = null;
     private IRemoteCallback mClientAnimationFinishCallback = null;
 
-    private @TransitionState int mState = STATE_COLLECTING;
+    private @TransitionState int mState = STATE_PENDING;
     private final ReadyTracker mReadyTracker = new ReadyTracker();
 
     // TODO(b/188595497): remove when not needed.
@@ -179,13 +183,12 @@
     private boolean mNavBarAttachedToApp = false;
     private int mRecentsDisplayId = INVALID_DISPLAY;
 
-    Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
+    Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
         mFlags = flags;
         mController = controller;
         mSyncEngine = syncEngine;
-        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
     }
 
     void addFlag(int flag) {
@@ -216,13 +219,24 @@
         return mFlags;
     }
 
+    /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+    void startCollecting(long timeoutMs) {
+        if (mState != STATE_PENDING) {
+            throw new IllegalStateException("Attempting to re-use a transition");
+        }
+        mState = STATE_COLLECTING;
+        mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+    }
+
     /**
      * Formally starts the transition. Participants can be collected before this is started,
      * but this won't consider itself ready until started -- even if all the participants have
      * drawn.
      */
     void start() {
-        if (mState >= STATE_STARTED) {
+        if (mState < STATE_COLLECTING) {
+            throw new IllegalStateException("Can't start Transition which isn't collecting.");
+        } else if (mState >= STATE_STARTED) {
             Slog.w(TAG, "Transition already started: " + mSyncId);
         }
         mState = STATE_STARTED;
@@ -235,6 +249,9 @@
      * Adds wc to set of WindowContainers participating in this transition.
      */
     void collect(@NonNull WindowContainer wc) {
+        if (mState < STATE_COLLECTING) {
+            throw new IllegalStateException("Transition hasn't started collecting.");
+        }
         if (mSyncId < 0) return;
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
                 mSyncId, wc);
@@ -368,7 +385,7 @@
         // usually only size 1
         final ArraySet<DisplayContent> displays = new ArraySet<>();
         for (int i = mTargets.size() - 1; i >= 0; --i) {
-            final WindowContainer target = mTargets.valueAt(i);
+            final WindowContainer target = mTargets.get(i);
             if (target.getParent() != null) {
                 final SurfaceControl targetLeash = getLeashSurface(target);
                 final SurfaceControl origParent = getOrigParentSurface(target);
@@ -493,10 +510,10 @@
             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
         }
 
-        final FadeRotationAnimationController fadeRotationController =
-                mTargetDisplay.getFadeRotationAnimationController();
-        if (fadeRotationController != null) {
-            fadeRotationController.onTransitionFinished();
+        final AsyncRotationController asyncRotationController =
+                mTargetDisplay.getAsyncRotationController();
+        if (asyncRotationController != null) {
+            asyncRotationController.onTransitionFinished();
         }
         // Transient-launch activities cannot be IME target (WindowState#canBeImeTarget),
         // so re-compute in case the IME target is changed after transition.
@@ -638,7 +655,7 @@
 
         // This is non-null only if display has changes. It handles the visible windows that don't
         // need to be participated in the transition.
-        final FadeRotationAnimationController controller = dc.getFadeRotationAnimationController();
+        final AsyncRotationController controller = dc.getAsyncRotationController();
         if (controller != null) {
             controller.setupStartTransaction(transaction);
         }
@@ -728,7 +745,7 @@
 
         if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
                 // Skip the case where the nav bar is controlled by fade rotation.
-                || dc.getFadeRotationAnimationController() != null) {
+                || dc.getAsyncRotationController() != null) {
             return;
         }
 
@@ -801,7 +818,7 @@
         // Search for the home task. If it is supposed to be visible, then the navbar is not at
         // the bottom of the screen, so we need to animate it.
         for (int i = 0; i < mTargets.size(); ++i) {
-            final Task task = mTargets.valueAt(i).asTask();
+            final Task task = mTargets.get(i).asTask();
             if (task == null || !task.isHomeOrRecentsRootTask()) continue;
             animate = task.isVisibleRequested();
             break;
@@ -884,23 +901,8 @@
     private static boolean reportIfNotTop(WindowContainer wc) {
         // Organized tasks need to be reported anyways because Core won't show() their surfaces
         // and we can't rely on onTaskAppeared because it isn't in sync.
-        // Also report wallpaper so it can be handled properly during display change/rotation.
         // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
-        return wc.isOrganized() || isWallpaper(wc);
-    }
-
-    /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
-    private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
-        WindowContainer parent = child;
-        int depth = 0;
-        while (parent != null) {
-            if (parent == ancestor) {
-                return depth;
-            }
-            parent = parent.getParent();
-            ++depth;
-        }
-        return -1;
+        return wc.isOrganized();
     }
 
     private static boolean isWallpaper(WindowContainer wc) {
@@ -930,61 +932,48 @@
      *
      * @return {@code true} if transition in target can be promoted to its parent.
      */
-    private static boolean canPromote(WindowContainer target, ArraySet<WindowContainer> topTargets,
+    private static boolean canPromote(WindowContainer<?> target, Targets targets,
             ArrayMap<WindowContainer, ChangeInfo> changes) {
-        final WindowContainer parent = target.getParent();
-        final ChangeInfo parentChanges = parent != null ? changes.get(parent) : null;
-        if (parent == null || !parent.canCreateRemoteAnimationTarget()
-                || parentChanges == null || !parentChanges.hasChanged(parent)) {
+        final WindowContainer<?> parent = target.getParent();
+        final ChangeInfo parentChange = changes.get(parent);
+        if (!parent.canCreateRemoteAnimationTarget()
+                || parentChange == null || !parentChange.hasChanged(parent)) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
-                    parent == null ? "no parent" : ("parent can't be target " + parent));
+                    "parent can't be target " + parent);
             return false;
         }
         if (isWallpaper(target)) {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
             return false;
         }
-        @TransitionInfo.TransitionMode int mode = TRANSIT_NONE;
-        // Go through all siblings of this target to see if any of them would prevent
-        // the target from promoting.
-        siblingLoop:
+
+        final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-            final WindowContainer sibling = parent.getChildAt(i);
+            final WindowContainer<?> sibling = parent.getChildAt(i);
+            if (target == sibling) continue;
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
                     sibling);
-            // Check if any topTargets are the sibling or within it
-            for (int j = topTargets.size() - 1; j >= 0; --j) {
-                final int depth = getChildDepth(topTargets.valueAt(j), sibling);
-                if (depth < 0) continue;
-                if (depth == 0) {
-                    final int siblingMode = changes.get(sibling).getTransitMode(sibling);
+            final ChangeInfo siblingChange = changes.get(sibling);
+            if (siblingChange == null || !targets.wasParticipated(sibling)) {
+                if (sibling.isVisibleRequested()) {
+                    // Sibling is visible but not animating, so no promote.
                     ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "        sibling is a top target with mode %s",
-                            TransitionInfo.modeToString(siblingMode));
-                    if (mode == TRANSIT_NONE) {
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "          no common mode yet, so set it");
-                        mode = siblingMode;
-                    } else if (mode != siblingMode) {
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "          SKIP: common mode mismatch. was %s",
-                                TransitionInfo.modeToString(mode));
-                        return false;
-                    }
-                    continue siblingLoop;
-                } else {
-                    // Sibling subtree may not be promotable.
-                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "        SKIP: sibling contains top target %s",
-                            topTargets.valueAt(j));
+                            "        SKIP: sibling is visible but not part of transition");
                     return false;
                 }
-            }
-            // No other animations are playing in this sibling
-            if (sibling.isVisibleRequested()) {
-                // Sibling is visible but not animating, so no promote.
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "        SKIP: sibling is visible but not part of transition");
+                        "        unrelated invisible sibling %s", sibling);
+                continue;
+            }
+
+            final int siblingMode = siblingChange.getTransitMode(sibling);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "        sibling is a participant with mode %s",
+                    TransitionInfo.modeToString(siblingMode));
+            if (mode != siblingMode) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "          SKIP: common mode mismatch. was %s",
+                        TransitionInfo.modeToString(mode));
                 return false;
             }
         }
@@ -994,58 +983,40 @@
     /**
      * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
      *
-     * @param topTargets set of just the top-most targets in the hierarchy of participants.
      * @param targets all targets that will be sent to the player.
-     * @return {@code true} if something was promoted.
      */
-    private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
-            ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  --- Start combine pass ---");
-        // Go through each target until we find one that can be promoted.
-        for (WindowContainer targ : topTargets) {
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", targ);
-            if (!canPromote(targ, topTargets, changes)) {
+    private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+        WindowContainer<?> lastNonPromotableParent = null;
+        // Go through from the deepest target.
+        for (int i = targets.mArray.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> target = targets.mArray.valueAt(i);
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
+            final WindowContainer<?> parent = target.getParent();
+            if (parent == lastNonPromotableParent) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "      SKIP: its sibling was rejected");
                 continue;
             }
-            // No obstructions found to promotion, so promote
-            final WindowContainer parent = targ.getParent();
-            final ChangeInfo parentInfo = changes.get(parent);
-            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                    "      CAN PROMOTE: promoting to parent %s", parent);
-            targets.add(parent);
-
-            // Go through all children of newly-promoted container and remove them from the
-            // top-targets.
-            for (int i = parent.getChildCount() - 1; i >= 0; --i) {
-                final WindowContainer child = parent.getChildAt(i);
-                int idx = targets.indexOf(child);
-                if (idx >= 0) {
-                    final ChangeInfo childInfo = changes.get(child);
-                    if (reportIfNotTop(child)) {
-                        childInfo.mParent = parent;
-                        parentInfo.addChild(child);
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "        keep as target %s", child);
-                    } else {
-                        if (childInfo.mChildren != null) {
-                            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                    "        merging children in from %s: %s", child,
-                                    childInfo.mChildren);
-                            parentInfo.addChildren(childInfo.mChildren);
-                        }
-                        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                                "        remove from targets %s", child);
-                        targets.removeAt(idx);
-                    }
-                }
-                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                        "        remove from topTargets %s", child);
-                topTargets.remove(child);
+            if (!canPromote(target, targets, changes)) {
+                lastNonPromotableParent = parent;
+                continue;
             }
-            topTargets.add(parent);
-            return true;
+            if (reportIfNotTop(target)) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        keep as target %s", target);
+            } else {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "        remove from targets %s", target);
+                targets.remove(i, target);
+            }
+            if (targets.mArray.indexOfValue(parent) < 0) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "      CAN PROMOTE: promoting to parent %s", parent);
+                // The parent has lower depth, so it will be checked in the later iteration.
+                i++;
+                targets.add(parent);
+            }
         }
-        return false;
     }
 
     /**
@@ -1054,22 +1025,15 @@
      */
     @VisibleForTesting
     @NonNull
-    static ArraySet<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
+    static ArrayList<WindowContainer> calculateTargets(ArraySet<WindowContainer> participants,
             ArrayMap<WindowContainer, ChangeInfo> changes) {
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Start calculating TransitionInfo based on participants: %s", participants);
 
-        final ArraySet<WindowContainer> topTargets = new ArraySet<>();
-        // The final animation targets which cannot promote to higher level anymore.
-        final ArraySet<WindowContainer> targets = new ArraySet<>();
-
-        final ArrayList<WindowContainer> tmpList = new ArrayList<>();
-
-        // Build initial set of top-level participants by removing any participants that are no-ops
-        // or children of other participants or are otherwise invalid; however, keep around a list
-        // of participants that should always be reported even if they aren't top.
-        for (WindowContainer wc : participants) {
-            // Don't include detached windows.
+        // Add all valid participants to the target container.
+        final Targets targets = new Targets();
+        for (int i = participants.size() - 1; i >= 0; --i) {
+            final WindowContainer<?> wc = participants.valueAt(i);
             if (!wc.isAttached()) {
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                         "  Rejecting as detached: %s", wc);
@@ -1084,70 +1048,62 @@
                         "  Rejecting as no-op: %s", wc);
                 continue;
             }
-
-            // Search through ancestors to find the top-most participant (if one exists)
-            WindowContainer topParent = null;
-            tmpList.clear();
-            if (reportIfNotTop(wc)) {
-                tmpList.add(wc);
-            }
-            // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
-            boolean skipIntermediateReports = isWallpaper(wc);
-            for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
-                if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) {
-                    // Again, we're skipping no-ops
-                    break;
-                }
-                if (participants.contains(p)) {
-                    topParent = p;
-                    break;
-                } else if (isWallpaper(p)) {
-                    skipIntermediateReports = true;
-                } else if (reportIfNotTop(p) && !skipIntermediateReports) {
-                    tmpList.add(p);
-                }
-            }
-            if (topParent != null) {
-                // There was an ancestor participant, so don't add wc to targets unless always-
-                // report. Similarly, add any always-report parents along the way.
-                for (int i = 0; i < tmpList.size(); ++i) {
-                    targets.add(tmpList.get(i));
-                    final ChangeInfo info = changes.get(tmpList.get(i));
-                    info.mParent = i < tmpList.size() - 1 ? tmpList.get(i + 1) : topParent;
-                }
-                continue;
-            }
-            // No ancestors in participant-list, so wc is a top target.
             targets.add(wc);
-            topTargets.add(wc);
+            targets.mValidParticipants.add(wc);
         }
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
+                targets.mArray);
+        // Combine the targets from bottom to top if possible.
+        tryPromote(targets, changes);
+        // Establish the relationship between the targets and their top changes.
+        populateParentChanges(targets, changes);
 
-        // Populate children lists
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            if (changes.get(targets.valueAt(i)).mParent != null) {
-                changes.get(changes.get(targets.valueAt(i)).mParent).addChild(targets.valueAt(i));
-            }
-        }
-
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s", targets);
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Top targets: %s", topTargets);
-
-        // Combine targets by repeatedly going through the topTargets to see if they can be
-        // promoted until there aren't any promotions possible.
-        while (tryPromote(topTargets, targets, changes)) {
-            // Empty on purpose
-        }
-        return targets;
+        final ArrayList<WindowContainer> targetList = targets.getListSortedByZ();
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
+        return targetList;
     }
 
-    /** Add any of `members` within `root` to `out` in top-to-bottom z-order. */
-    private static void addMembersInOrder(WindowContainer root, ArraySet<WindowContainer> members,
-            ArrayList<WindowContainer> out) {
-        for (int i = root.getChildCount() - 1; i >= 0; --i) {
-            final WindowContainer child = root.getChildAt(i);
-            addMembersInOrder(child, members, out);
-            if (members.contains(child)) {
-                out.add(child);
+    /** Populates parent to the change info and collects intermediate targets. */
+    private static void populateParentChanges(Targets targets,
+            ArrayMap<WindowContainer, ChangeInfo> changes) {
+        final ArrayList<WindowContainer<?>> intermediates = new ArrayList<>();
+        for (int i = targets.mValidParticipants.size() - 1; i >= 0; --i) {
+            WindowContainer<?> wc = targets.mValidParticipants.get(i);
+            // Go up if the participant has been represented by its parent.
+            while (targets.mArray.indexOfValue(wc) < 0 && wc.getParent() != null) {
+                wc = wc.getParent();
+            }
+            // Wallpaper must belong to the top (regardless of how nested it is in DisplayAreas).
+            final boolean skipIntermediateReports = isWallpaper(wc);
+            intermediates.clear();
+            // Collect the intermediate parents between target and top changed parent.
+            for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+                final ChangeInfo parentChange = changes.get(p);
+                if (parentChange == null || !parentChange.hasChanged(p)) break;
+                if (parentChange.mParent != null && !skipIntermediateReports) {
+                    changes.get(wc).mParent = p;
+                    // The chain above the parent was processed.
+                    break;
+                }
+                if (targets.mValidParticipants.contains(p)) {
+                    if (skipIntermediateReports) {
+                        changes.get(wc).mParent = p;
+                    } else {
+                        intermediates.add(p);
+                    }
+                    // The parent reaches a participant.
+                    break;
+                } else if (reportIfNotTop(p) && !skipIntermediateReports) {
+                    intermediates.add(p);
+                }
+            }
+            if (intermediates.isEmpty()) continue;
+            // Add any always-report parents along the way.
+            changes.get(wc).mParent = intermediates.get(0);
+            for (int j = 0; j < intermediates.size() - 1; j++) {
+                final WindowContainer<?> intermediate = intermediates.get(j);
+                changes.get(intermediate).mParent = intermediates.get(j + 1);
+                targets.add(intermediate);
             }
         }
     }
@@ -1184,32 +1140,36 @@
     /**
      * Construct a TransitionInfo object from a set of targets and changes. Also populates the
      * root surface.
+     * @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
      */
     @VisibleForTesting
     @NonNull
     static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
-            ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
+            ArrayList<WindowContainer> sortedTargets,
+            ArrayMap<WindowContainer, ChangeInfo> changes) {
         final TransitionInfo out = new TransitionInfo(type, flags);
 
-        final ArraySet<WindowContainer> appTargets = new ArraySet<>();
-        final ArraySet<WindowContainer> wallpapers = new ArraySet<>();
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            (isWallpaper(targets.valueAt(i)) ? wallpapers : appTargets).add(targets.valueAt(i));
+        WindowContainer<?> topApp = null;
+        for (int i = 0; i < sortedTargets.size(); i++) {
+            final WindowContainer<?> wc = sortedTargets.get(i);
+            if (!isWallpaper(wc)) {
+                topApp = wc;
+                break;
+            }
         }
-
-        // Find the top-most shared ancestor of app targets
-        if (appTargets.isEmpty()) {
+        if (topApp == null) {
             out.setRootLeash(new SurfaceControl(), 0, 0);
             return out;
         }
-        WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
 
+        // Find the top-most shared ancestor of app targets.
+        WindowContainer<?> ancestor = topApp.getParent();
         // Go up ancestor parent chain until all targets are descendants.
         ancestorLoop:
         while (ancestor != null) {
-            for (int i = appTargets.size() - 1; i >= 0; --i) {
-                final WindowContainer wc = appTargets.valueAt(i);
-                if (!wc.isDescendantOf(ancestor)) {
+            for (int i = sortedTargets.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = sortedTargets.get(i);
+                if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
                     ancestor = ancestor.getParent();
                     continue ancestorLoop;
                 }
@@ -1217,11 +1177,6 @@
             break;
         }
 
-        // Sort targets top-to-bottom in Z. Check ALL targets here in case the display area itself
-        // is animating: then we want to include wallpapers at the right position.
-        ArrayList<WindowContainer> sortedTargets = new ArrayList<>();
-        addMembersInOrder(ancestor, targets, sortedTargets);
-
         // make leash based on highest (z-order) direct child of ancestor with a participant.
         WindowContainer leashReference = sortedTargets.get(0);
         while (leashReference.getParent() != ancestor) {
@@ -1235,14 +1190,6 @@
         t.close();
         out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
 
-        // add the wallpapers at the bottom
-        for (int i = wallpapers.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = wallpapers.valueAt(i);
-            // If the displayarea itself is animating, then the wallpaper was already added.
-            if (wc.isDescendantOf(ancestor)) break;
-            sortedTargets.add(wc);
-        }
-
         // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
         final int count = sortedTargets.size();
         for (int i = 0; i < count; ++i) {
@@ -1386,7 +1333,6 @@
     static class ChangeInfo {
         // Usually "post" change state.
         WindowContainer mParent;
-        ArraySet<WindowContainer> mChildren;
 
         // State tracking
         boolean mExistenceChanged = false;
@@ -1481,19 +1427,6 @@
             }
             return flags;
         }
-
-        void addChild(@NonNull WindowContainer wc) {
-            if (mChildren == null) {
-                mChildren = new ArraySet<>();
-            }
-            mChildren.add(wc);
-        }
-        void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
-            if (mChildren == null) {
-                mChildren = new ArraySet<>();
-            }
-            mChildren.addAll(wcs);
-        }
     }
 
     /**
@@ -1586,4 +1519,64 @@
             return b.toString();
         }
     }
+
+    /**
+     * The container to represent the depth relation for calculating transition targets. The window
+     * container with larger depth is put at larger index. For the same depth, higher z-order has
+     * larger index.
+     */
+    private static class Targets {
+        /** All targets. Its keys (depth) are sorted in ascending order naturally. */
+        final SparseArray<WindowContainer<?>> mArray = new SparseArray<>();
+        /** The initial participants which have changes. */
+        final ArrayList<WindowContainer<?>> mValidParticipants = new ArrayList<>();
+        /** The targets which were represented by their parent. */
+        private ArrayList<WindowContainer<?>> mRemovedTargets;
+        private int mDepthFactor;
+
+        void add(WindowContainer<?> target) {
+            // The number of slots per depth is larger than the total number of window container,
+            // so the depth score (key) won't have collision.
+            if (mDepthFactor == 0) {
+                mDepthFactor = target.mWmService.mRoot.getTreeWeight() + 1;
+            }
+            int score = target.getPrefixOrderIndex();
+            WindowContainer<?> wc = target;
+            while (wc != null) {
+                final WindowContainer<?> parent = wc.getParent();
+                if (parent != null) {
+                    score += mDepthFactor;
+                }
+                wc = parent;
+            }
+            mArray.put(score, target);
+        }
+
+        void remove(int index, WindowContainer<?> removingTarget) {
+            mArray.removeAt(index);
+            if (mRemovedTargets == null) {
+                mRemovedTargets = new ArrayList<>();
+            }
+            mRemovedTargets.add(removingTarget);
+        }
+
+        boolean wasParticipated(WindowContainer<?> wc) {
+            return mArray.indexOfValue(wc) >= 0
+                    || (mRemovedTargets != null && mRemovedTargets.contains(wc));
+        }
+
+        /** Returns the target list sorted by z-order in ascending order (index 0 is top). */
+        ArrayList<WindowContainer> getListSortedByZ() {
+            final SparseArray<WindowContainer<?>> arrayByZ = new SparseArray<>(mArray.size());
+            for (int i = mArray.size() - 1; i >= 0; --i) {
+                final int zOrder = mArray.keyAt(i) % mDepthFactor;
+                arrayByZ.put(zOrder, mArray.valueAt(i));
+            }
+            final ArrayList<WindowContainer> sortedTargets = new ArrayList<>(arrayByZ.size());
+            for (int i = arrayByZ.size() - 1; i >= 0; --i) {
+                sortedTargets.add(arrayByZ.valueAt(i));
+            }
+            return sortedTargets;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index fe968ec..7a031db 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -128,16 +128,46 @@
             throw new IllegalStateException("Shell Transitions not enabled");
         }
         if (mCollectingTransition != null) {
-            throw new IllegalStateException("Simultaneous transitions not supported yet.");
+            throw new IllegalStateException("Simultaneous transition collection not supported"
+                    + " yet. Use {@link #createPendingTransition} for explicit queueing.");
         }
+        Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
+        moveToCollecting(transit);
+        return transit;
+    }
+
+    /** Starts Collecting */
+    private void moveToCollecting(@NonNull Transition transition) {
+        if (mCollectingTransition != null) {
+            throw new IllegalStateException("Simultaneous transition collection not supported.");
+        }
+        mCollectingTransition = transition;
         // Distinguish change type because the response time is usually expected to be not too long.
-        final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
-        mCollectingTransition = new Transition(type, flags, timeoutMs, this,
-                mAtm.mWindowManager.mSyncEngine);
-        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+        final long timeoutMs =
+                transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
+        mCollectingTransition.startCollecting(timeoutMs);
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
                 mCollectingTransition);
         dispatchLegacyAppTransitionPending();
-        return mCollectingTransition;
+    }
+
+    /** Creates a transition representation but doesn't start collecting. */
+    @NonNull
+    PendingStartTransition createPendingTransition(@WindowManager.TransitionType int type) {
+        if (mTransitionPlayer == null) {
+            throw new IllegalStateException("Shell Transitions not enabled");
+        }
+        final PendingStartTransition out = new PendingStartTransition(new Transition(type,
+                0 /* flags */, this, mAtm.mWindowManager.mSyncEngine));
+        // We want to start collecting immediately when the engine is free, otherwise it may
+        // be busy again.
+        out.setStartSync(() -> {
+            moveToCollecting(out.mTransition);
+        });
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating PendingTransition: %s",
+                out.mTransition);
+        return out;
     }
 
     void registerTransitionPlayer(@Nullable ITransitionPlayer player,
@@ -507,6 +537,15 @@
         proto.end(token);
     }
 
+    /** Represents a startTransition call made while there is other active BLAST SyncGroup. */
+    class PendingStartTransition extends WindowOrganizerController.PendingTransaction {
+        final Transition mTransition;
+
+        PendingStartTransition(Transition transition) {
+            mTransition = transition;
+        }
+    }
+
     static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
         private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index fc154a8..36bb55e 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -28,9 +28,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.view.DisplayInfo;
-import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.animation.Animation;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -190,23 +187,6 @@
         setVisible(visible);
     }
 
-    @Override
-    void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
-        if (attrs.height == ViewGroup.LayoutParams.MATCH_PARENT
-                || attrs.width == ViewGroup.LayoutParams.MATCH_PARENT) {
-            return;
-        }
-
-        final DisplayInfo displayInfo = win.getDisplayInfo();
-
-        final float layoutScale = Math.max(
-                (float) displayInfo.logicalHeight / (float) attrs.height,
-                (float) displayInfo.logicalWidth / (float) attrs.width);
-        attrs.height = (int) (attrs.height * layoutScale);
-        attrs.width = (int) (attrs.width * layoutScale);
-        attrs.flags |= WindowManager.LayoutParams.FLAG_SCALED;
-    }
-
     boolean hasVisibleNotDrawnWallpaper() {
         if (!isVisible()) return false;
         for (int j = mChildren.size() - 1; j >= 0; --j) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4006848..11d1983 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -85,8 +85,8 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
 import android.view.SurfaceControl.Builder;
+import android.view.SurfaceControlViewHost;
 import android.view.SurfaceSession;
 import android.view.TaskTransitionSpec;
 import android.view.WindowManager;
@@ -661,6 +661,11 @@
         }
     }
 
+    /** Returns the total number of descendants, including self. */
+    int getTreeWeight() {
+        return mTreeWeight;
+    }
+
     /**
      * @return The index of this element in the hierarchy tree in prefix order.
      */
@@ -3331,7 +3336,10 @@
         ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "setSyncGroup #%d on %s", group.mSyncId, this);
         if (group != null) {
             if (mSyncGroup != null && mSyncGroup != group) {
-                throw new IllegalStateException("Can't sync on 2 engines simultaneously");
+                // This can still happen if WMCore starts a new transition when there is ongoing
+                // sync transaction from Shell. Please file a bug if it happens.
+                throw new IllegalStateException("Can't sync on 2 engines simultaneously"
+                        + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
             }
         }
         mSyncGroup = group;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1a8ceb..3ba332b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2105,6 +2105,19 @@
         Slog.i(tag, s, e);
     }
 
+    void clearTouchableRegion(Session session, IWindow client) {
+        int uid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                WindowState w = windowForClientLocked(session, client, false);
+                w.clearClientTouchableRegion();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
             Rect visibleInsets, Region touchableRegion) {
         int uid = Binder.getCallingUid();
@@ -2130,6 +2143,7 @@
                     }
                     w.setDisplayLayoutNeeded();
                     mWindowPlacerLocked.performSurfacePlacement();
+                    w.getDisplayContent().getInputMonitor().updateInputWindowsLw(true);
 
                     // We need to report touchable region changes to accessibility.
                     if (mAccessibilityController.hasCallbacks()) {
@@ -2180,9 +2194,9 @@
 
     public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewVisibility, int flags,
-            long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
+            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
             SurfaceControl outSurfaceControl, InsetsState outInsetsState,
-            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
+            InsetsSourceControl[] outActiveControls) {
         Arrays.fill(outActiveControls, null);
         int result = 0;
         boolean configChanged;
@@ -2202,14 +2216,11 @@
                 win.setRequestedSize(requestedWidth, requestedHeight);
             }
 
-            win.setFrameNumber(frameNumber);
-
             int attrChanges = 0;
             int flagChanges = 0;
             int privateFlagChanges = 0;
             if (attrs != null) {
                 displayPolicy.adjustWindowParamsLw(win, attrs);
-                win.mToken.adjustWindowParams(win, attrs);
                 attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
                 attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), uid,
                         pid);
@@ -2497,11 +2508,6 @@
                 displayContent.sendNewConfiguration();
                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
-            if (winAnimator.mSurfaceController != null) {
-                win.calculateSurfaceBounds(win.getLayoutingAttrs(
-                        win.getWindowConfiguration().getRotation()), mTmpRect);
-                outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
-            }
             getInsetsSourceControls(win, outActiveControls);
         }
 
@@ -2580,7 +2586,7 @@
         WindowSurfaceController surfaceController;
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
-            surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type);
+            surfaceController = winAnimator.createSurfaceLocked();
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -5680,6 +5686,11 @@
     }
 
     @Override
+    public void saveWindowTraceToFile() {
+        mWindowTracing.saveForBugreport(null /* printwriter */);
+    }
+
+    @Override
     public boolean isWindowTraceEnabled() {
         return mWindowTracing.isEnabled();
     }
@@ -6415,7 +6426,6 @@
         pw.print("  mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
         pw.print("  mHasPermanentDpad="); pw.println(mHasPermanentDpad);
         mRoot.dumpTopFocusedDisplayId(pw);
-        mRoot.dumpDefaultMinSizeOfResizableTask(pw);
         mRoot.forAllDisplays(dc -> {
             final int displayId = dc.getDisplayId();
             final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
@@ -6433,6 +6443,8 @@
                 pw.print("  imeControlTarget in display# "); pw.print(displayId);
                 pw.print(' '); pw.println(imeControlTarget);
             }
+            pw.print("  Minimum task size of display#"); pw.print(displayId);
+            pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
         });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
         pw.print("  mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 455856c..27024ce 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -76,6 +76,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -94,7 +95,7 @@
  * @see android.window.WindowOrganizer
  */
 class WindowOrganizerController extends IWindowOrganizerController.Stub
-    implements BLASTSyncEngine.TransactionReadyListener {
+        implements BLASTSyncEngine.TransactionReadyListener, BLASTSyncEngine.SyncEngineListener {
 
     private static final String TAG = "WindowOrganizerController";
 
@@ -117,6 +118,21 @@
     private final HashMap<Integer, IWindowContainerTransactionCallback>
             mTransactionCallbacksByPendingSyncId = new HashMap();
 
+    /**
+     * A queue of transaction waiting for their turn to sync. Currently {@link BLASTSyncEngine} only
+     * supports 1 sync at a time, so we have to queue them.
+     *
+     * WMCore has enough information to ensure that it won't end up collecting multiple transitions
+     * in parallel by itself; however, Shell can start transitions/apply sync transaction at
+     * arbitrary times via {@link WindowOrganizerController#startTransition} and
+     * {@link WindowOrganizerController#applySyncTransaction}, so we have to support those coming in
+     * at any time (even while already syncing).
+     *
+     * This is really just a back-up for unrealistic situations (eg. during tests). In practice,
+     * this shouldn't ever happen.
+     */
+    private final ArrayList<PendingTransaction> mPendingTransactions = new ArrayList<>();
+
     final TaskOrganizerController mTaskOrganizerController;
     final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
     final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
@@ -140,6 +156,7 @@
     void setWindowManager(WindowManagerService wms) {
         mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController);
         mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
+        wms.mSyncEngine.setSyncEngineListener(this);
     }
 
     TransitionController getTransitionController() {
@@ -184,6 +201,11 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                if (callback == null) {
+                    applyTransaction(t, -1 /* syncId*/, null /*transition*/, caller);
+                    return -1;
+                }
+
                 /**
                  * If callback is non-null we are looking to synchronize this transaction by
                  * collecting all the results in to a SurfaceFlinger transaction and then delivering
@@ -196,13 +218,25 @@
                  * all the WindowContainers will eventually finish applying their changes and notify
                  * the BLASTSyncEngine which will deliver the Transaction to the callback.
                  */
-                int syncId = -1;
-                if (callback != null) {
-                    syncId = startSyncWithOrganizer(callback);
-                }
-                applyTransaction(t, syncId, null /*transition*/, caller);
-                if (syncId >= 0) {
+                final BLASTSyncEngine.SyncGroup syncGroup = prepareSyncWithOrganizer(callback);
+                final int syncId = syncGroup.mSyncId;
+                if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+                    mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
+                    applyTransaction(t, syncId, null /*transition*/, caller);
                     setSyncReady(syncId);
+                } else {
+                    // Because the BLAST engine only supports one sync at a time, queue the
+                    // transaction.
+                    final PendingTransaction pt = new PendingTransaction();
+                    // Start sync group immediately when the SyncEngine is free.
+                    pt.setStartSync(() ->
+                            mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup));
+                    // Those will be post so that it won't interrupt ongoing transition.
+                    pt.setStartTransaction(() -> {
+                        applyTransaction(t, syncId, null /*transition*/, caller);
+                        setSyncReady(syncId);
+                    });
+                    mPendingTransactions.add(pt);
                 }
                 return syncId;
             }
@@ -220,31 +254,49 @@
         try {
             synchronized (mGlobalLock) {
                 Transition transition = Transition.fromBinder(transitionToken);
+                if (mTransitionController.getTransitionPlayer() == null && transition == null) {
+                    Slog.w(TAG, "Using shell transitions API for legacy transitions.");
+                    if (t == null) {
+                        throw new IllegalArgumentException("Can't use legacy transitions in"
+                                + " compatibility mode with no WCT.");
+                    }
+                    applyTransaction(t, -1 /* syncId */, null, caller);
+                    return null;
+                }
                 // In cases where transition is already provided, the "readiness lifecycle" of the
                 // transition is determined outside of this transaction. However, if this is a
                 // direct call from shell, the entire transition lifecycle is contained in the
                 // provided transaction and thus we can setReady immediately after apply.
-                boolean needsSetReady = transition == null && t != null;
+                final boolean needsSetReady = transition == null && t != null;
+                final WindowContainerTransaction wct =
+                        t != null ? t : new WindowContainerTransaction();
                 if (transition == null) {
                     if (type < 0) {
                         throw new IllegalArgumentException("Can't create transition with no type");
                     }
-                    if (mTransitionController.getTransitionPlayer() == null) {
-                        Slog.w(TAG, "Using shell transitions API for legacy transitions.");
-                        if (t == null) {
-                            throw new IllegalArgumentException("Can't use legacy transitions in"
-                                    + " compatibility mode with no WCT.");
-                        }
-                        applyTransaction(t, -1 /* syncId */, null, caller);
-                        return null;
+                    // If there is already a collecting transition, queue up a new transition and
+                    // return that. The actual start and apply will then be deferred until that
+                    // transition starts collecting. This should almost never happen except during
+                    // tests.
+                    if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+                        Slog.e(TAG, "startTransition() while one is already collecting.");
+                        final TransitionController.PendingStartTransition pt =
+                                mTransitionController.createPendingTransition(type);
+                        // Those will be post so that it won't interrupt ongoing transition.
+                        pt.setStartTransaction(() -> {
+                            pt.mTransition.start();
+                            applyTransaction(wct, -1 /*syncId*/, pt.mTransition, caller);
+                            if (needsSetReady) {
+                                pt.mTransition.setAllReady();
+                            }
+                        });
+                        mPendingTransactions.add(pt);
+                        return pt.mTransition;
                     }
                     transition = mTransitionController.createTransition(type);
                 }
                 transition.start();
-                if (t == null) {
-                    t = new WindowContainerTransaction();
-                }
-                applyTransaction(t, -1 /*syncId*/, transition, caller);
+                applyTransaction(wct, -1 /*syncId*/, transition, caller);
                 if (needsSetReady) {
                     transition.setAllReady();
                 }
@@ -1036,11 +1088,24 @@
         return mTaskFragmentOrganizerController;
     }
 
+    /**
+     * This will prepare a {@link BLASTSyncEngine.SyncGroup} for the organizer to track, but the
+     * {@link BLASTSyncEngine.SyncGroup} may not be active until the {@link BLASTSyncEngine} is
+     * free.
+     */
+    private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
+            IWindowContainerTransactionCallback callback) {
+        final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
+                .prepareSyncSet(this, "");
+        mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
+        return s;
+    }
+
     @VisibleForTesting
     int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
-        int id = mService.mWindowManager.mSyncEngine.startSyncSet(this);
-        mTransactionCallbacksByPendingSyncId.put(id, callback);
-        return id;
+        final BLASTSyncEngine.SyncGroup s = prepareSyncWithOrganizer(callback);
+        mService.mWindowManager.mSyncEngine.startSyncSet(s);
+        return s.mSyncId;
     }
 
     @VisibleForTesting
@@ -1072,6 +1137,19 @@
     }
 
     @Override
+    public void onSyncEngineFree() {
+        if (mPendingTransactions.isEmpty()) {
+            return;
+        }
+
+        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "PendingStartTransaction found");
+        final PendingTransaction pt = mPendingTransactions.remove(0);
+        pt.startSync();
+        // Post this so that the now-playing transition setup isn't interrupted.
+        mService.mH.post(pt::startTransaction);
+    }
+
+    @Override
     public void registerTransitionPlayer(ITransitionPlayer player) {
         enforceTaskPermission("registerTransitionPlayer()");
         final int callerPid = Binder.getCallingPid();
@@ -1329,4 +1407,38 @@
                         + result + " when starting " + intent);
         }
     }
+
+    /**
+     *  Represents a sync {@link WindowContainerTransaction} call made while there is other active
+     *  {@link BLASTSyncEngine.SyncGroup}.
+     */
+    static class PendingTransaction {
+        private Runnable mStartSync;
+        private Runnable mStartTransaction;
+
+        /**
+         * The callback will be called immediately when the {@link BLASTSyncEngine} is free. One
+         * should call {@link BLASTSyncEngine#startSyncSet(BLASTSyncEngine.SyncGroup)} here to
+         * reserve the {@link BLASTSyncEngine}.
+         */
+        void setStartSync(@NonNull Runnable callback) {
+            mStartSync = callback;
+        }
+
+        /**
+         * The callback will be post to the main handler after the {@link BLASTSyncEngine} is free
+         * to apply the pending {@link WindowContainerTransaction}.
+         */
+        void setStartTransaction(@NonNull Runnable callback) {
+            mStartTransaction = callback;
+        }
+
+        private void startSync() {
+            mStartSync.run();
+        }
+
+        private void startTransaction() {
+            mStartTransaction.run();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1f83767..a228d6a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -163,7 +163,6 @@
 import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
 import static com.android.server.wm.WindowStateProto.DESTROYING;
 import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
-import static com.android.server.wm.WindowStateProto.FINISHED_SEAMLESS_ROTATION_FRAME;
 import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
 import static com.android.server.wm.WindowStateProto.GLOBAL_SCALE;
@@ -380,7 +379,6 @@
      */
     final boolean mForceSeamlesslyRotate;
     SeamlessRotator mPendingSeamlessRotate;
-    long mFinishSeamlessRotateFrameNumber;
 
     private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
 
@@ -654,6 +652,7 @@
 
     private final Rect mTmpRect = new Rect();
     private final Point mTmpPoint = new Point();
+    private final Region mTmpRegion = new Region();
 
     private final Transaction mTmpTransaction;
 
@@ -706,11 +705,6 @@
      */
     private PowerManagerWrapper mPowerManagerWrapper;
 
-    /**
-     * A frame number in which changes requested in this layout will be rendered.
-     */
-    private long mFrameNumber = -1;
-
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
@@ -969,7 +963,6 @@
         }
 
         mPendingSeamlessRotate.finish(t, this);
-        mFinishSeamlessRotateFrameNumber = getFrameNumber();
         mPendingSeamlessRotate = null;
 
         getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -1418,8 +1411,8 @@
         return mWindowFrames.mParentFrame;
     }
 
-    void getCompatFrameSize(Rect outFrame) {
-        outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
+    Rect getCompatFrame() {
+        return mWindowFrames.mCompatFrame;
     }
 
     WindowManager.LayoutParams getAttrs() {
@@ -1563,7 +1556,7 @@
             }
         } else {
             // The orientation change is completed. If it was hidden by the animation, reshow it.
-            mDisplayContent.finishFadeRotationAnimation(mToken);
+            mDisplayContent.finishAsyncRotation(mToken);
         }
     }
 
@@ -1608,6 +1601,15 @@
         return getDisplayContent().getDisplayInfo();
     }
 
+    @Override
+    public Rect getMaxBounds() {
+        final Rect maxBounds = mToken.getFixedRotationTransformMaxBounds();
+        if (maxBounds != null) {
+            return maxBounds;
+        }
+        return super.getMaxBounds();
+    }
+
     /**
      * Returns the insets state for the window. Its sources may be the copies with visibility
      * modification according to the state of transient bars.
@@ -2773,6 +2775,7 @@
                 region.set(-dw, -dh, dw + dw, dh + dh);
             }
             subtractTouchExcludeRegionIfNeeded(region);
+
         } else {
             // Not modal
             getTouchableRegion(region);
@@ -2783,6 +2786,14 @@
         if (frame.left != 0 || frame.top != 0) {
             region.translate(-frame.left, -frame.top);
         }
+        if (modal && mTouchableInsets == TOUCHABLE_INSETS_REGION) {
+            // The client gave us a touchable region and so first
+            // we calculate the untouchable region, then punch that out of our
+            // expanded modal region.
+            mTmpRegion.set(0, 0, frame.right, frame.bottom);
+            mTmpRegion.op(mGivenTouchableRegion, Region.Op.DIFFERENCE);
+            region.op(mTmpRegion, Region.Op.DIFFERENCE);
+        }
 
         // TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
         // scaling but the existing logic doesn't expect that. The result is that the already-
@@ -4113,7 +4124,6 @@
         proto.write(IS_ON_SCREEN, isOnScreen());
         proto.write(IS_VISIBLE, isVisible);
         proto.write(PENDING_SEAMLESS_ROTATION, mPendingSeamlessRotate != null);
-        proto.write(FINISHED_SEAMLESS_ROTATION_FRAME, mFinishSeamlessRotateFrameNumber);
         proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
         proto.write(HAS_COMPAT_SCALE, hasCompatScale());
         proto.write(GLOBAL_SCALE, mGlobalScale);
@@ -4255,7 +4265,6 @@
         } else {
             pw.print("null");
         }
-        pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber);
 
         if (mHScale != 1 || mVScale != 1) {
             pw.println(prefix + "mHScale=" + mHScale
@@ -5624,16 +5633,6 @@
         return target != null ? target.getWindow() : null;
     }
 
-    long getFrameNumber() {
-        // Return the frame number in which changes requested in this layout will be rendered or
-        // -1 if we do not expect the frame to be rendered.
-        return getFrame().isEmpty() ? -1 : mFrameNumber;
-    }
-
-    void setFrameNumber(long frameNumber) {
-        mFrameNumber = frameNumber;
-    }
-
     void forceReportingResized() {
         mWindowFrames.forceReportingResized();
     }
@@ -5820,10 +5819,10 @@
 
         boolean skipLayout = false;
         // Control the timing to switch the appearance of window with different rotations.
-        final FadeRotationAnimationController fadeRotationController =
-                mDisplayContent.getFadeRotationAnimationController();
-        if (fadeRotationController != null
-                && fadeRotationController.handleFinishDrawing(this, postDrawTransaction)) {
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        if (asyncRotationController != null
+                && asyncRotationController.handleFinishDrawing(this, postDrawTransaction)) {
             // Consume the transaction because the controller will apply it with fade animation.
             // Layout is not needed because the window will be hidden by the fade leash. Clear
             // sync state because its sync transaction doesn't need to be merged to sync group.
@@ -5904,39 +5903,6 @@
         mRedrawForSyncReported = false;
     }
 
-    void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) {
-        outSize.setEmpty();
-        if ((attrs.flags & FLAG_SCALED) != 0) {
-            // For a scaled surface, we always want the requested size.
-            outSize.right = mRequestedWidth;
-            outSize.bottom = mRequestedHeight;
-        } else {
-            // When we're doing a drag-resizing, request a surface that's fullscreen size,
-            // so that we don't need to reallocate during the process. This also prevents
-            // buffer drops due to size mismatch.
-            if (isDragResizing()) {
-                final DisplayInfo displayInfo = getDisplayInfo();
-                outSize.right = displayInfo.logicalWidth;
-                outSize.bottom = displayInfo.logicalHeight;
-            } else {
-                getCompatFrameSize(outSize);
-            }
-        }
-
-        // This doesn't necessarily mean that there is an error in the system. The sizes might be
-        // incorrect, because it is before the first layout or draw.
-        if (outSize.width() < 1) {
-            outSize.right = 1;
-        }
-        if (outSize.height() < 1) {
-            outSize.bottom = 1;
-        }
-
-        // Adjust for surface insets.
-        outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
-                -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
-    }
-
     /**
      * This method is used to control whether we return the BLAST_SYNC flag
      * from relayoutWindow calls on this window (triggering the client to redirect
@@ -6062,4 +6028,9 @@
         }
         mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
     }
+
+    void clearClientTouchableRegion() {
+        mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+        mGivenTouchableRegion.setEmpty();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 316051e..c17961e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -150,8 +150,6 @@
 
     int mAttrType;
 
-    private final Rect mTmpSize = new Rect();
-
     /**
      * Handles surface changes synchronized to after the client has drawn the surface. This
      * transaction is currently used to reparent the old surface children to the new surface once
@@ -291,7 +289,7 @@
         }
     }
 
-    WindowSurfaceController createSurfaceLocked(int windowType) {
+    WindowSurfaceController createSurfaceLocked() {
         final WindowState w = mWin;
 
         if (mSurfaceController != null) {
@@ -319,16 +317,9 @@
             flags |= SurfaceControl.SKIP_SCREENSHOT;
         }
 
-        w.calculateSurfaceBounds(attrs, mTmpSize);
-
-        final int width = mTmpSize.width();
-        final int height = mTmpSize.height();
-
         if (DEBUG_VISIBILITY) {
             Slog.v(TAG, "Creating surface in session "
                     + mSession.mSurfaceSession + " window " + this
-                    + " w=" + width + " h=" + height
-                    + " x=" + mTmpSize.left + " y=" + mTmpSize.top
                     + " format=" + attrs.format + " flags=" + flags);
         }
 
@@ -339,8 +330,8 @@
             final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
             final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
 
-            mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
-                    height, format, flags, this, windowType);
+            mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
+                    flags, this, attrs.type);
             mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
                     & WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
 
@@ -372,8 +363,7 @@
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
             WindowManagerService.logSurface(w, "CREATE pos=("
-                    + w.getFrame().left + "," + w.getFrame().top + ") ("
-                    + width + "x" + height + ")" + " HIDE", false);
+                    + w.getFrame().left + "," + w.getFrame().top + ") HIDE", false);
         }
 
         mLastHidden = true;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index fa0d708..665857c 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -31,7 +31,6 @@
 import static com.android.server.wm.WindowSurfaceControllerProto.LAYER;
 import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
 
-import android.graphics.Region;
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
@@ -76,8 +75,8 @@
     // Used to track whether we have called detach children on the way to invisibility.
     boolean mChildrenDetached;
 
-    WindowSurfaceController(String name, int w, int h, int format,
-            int flags, WindowStateAnimator animator, int windowType) {
+    WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
+            int windowType) {
         mAnimator = animator;
 
         title = name;
@@ -91,7 +90,6 @@
         final SurfaceControl.Builder b = win.makeSurface()
                 .setParent(win.getSurfaceControl())
                 .setName(name)
-                .setBufferSize(w, h)
                 .setFormat(format)
                 .setFlags(flags)
                 .setMetadata(METADATA_WINDOW_TYPE, windowType)
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e5a3b7a..6d8203c 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -430,6 +430,13 @@
         return isFixedRotationTransforming() ? mFixedRotationTransformState.mDisplayFrames : null;
     }
 
+    Rect getFixedRotationTransformMaxBounds() {
+        return isFixedRotationTransforming()
+                ? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
+                .getMaxBounds()
+                : null;
+    }
+
     Rect getFixedRotationTransformDisplayBounds() {
         return isFixedRotationTransforming()
                 ? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
@@ -646,14 +653,6 @@
         }
     }
 
-    /**
-     * Gives a chance to this {@link WindowToken} to adjust the {@link
-     * android.view.WindowManager.LayoutParams} of its windows.
-     */
-    void adjustWindowParams(WindowState win, WindowManager.LayoutParams attrs) {
-    }
-
-
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
deleted file mode 100644
index 59abaab..0000000
--- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * Utility to compute bounds after rotating the screen.
- */
-public class DisplayRotationUtil {
-    private final Matrix mTmpMatrix = new Matrix();
-
-    private static int getRotationToBoundsOffset(int rotation) {
-        switch (rotation) {
-            case ROTATION_0:
-                return 0;
-            case ROTATION_90:
-                return -1;
-            case ROTATION_180:
-                return 2;
-            case ROTATION_270:
-                return 1;
-            default:
-                // should not happen
-                return 0;
-        }
-    }
-
-    @VisibleForTesting
-    static int getBoundIndexFromRotation(int i, int rotation) {
-        return Math.floorMod(i + getRotationToBoundsOffset(rotation),
-                BOUNDS_POSITION_LENGTH);
-    }
-
-    /**
-     * Compute bounds after rotating the screen.
-     *
-     * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
-     * @param rotation rotation constant defined in android.view.Surface.
-     * @param initialDisplayWidth width of the display before the rotation.
-     * @param initialDisplayHeight height of the display before the rotation.
-     * @return Bounds after the rotation.
-     *
-     * @hide
-     */
-    public Rect[] getRotatedBounds(
-            Rect[] bounds, int rotation, int initialDisplayWidth, int initialDisplayHeight) {
-        if (bounds.length != BOUNDS_POSITION_LENGTH) {
-            throw new IllegalArgumentException(
-                    "bounds must have exactly 4 elements: bounds=" + bounds);
-        }
-        if (rotation == ROTATION_0) {
-            return bounds;
-        }
-        transformPhysicalToLogicalCoordinates(rotation, initialDisplayWidth, initialDisplayHeight,
-                mTmpMatrix);
-        Rect[] newBounds = new Rect[BOUNDS_POSITION_LENGTH];
-        for (int i = 0; i < bounds.length; i++) {
-
-            final Rect rect = bounds[i];
-            if (!rect.isEmpty()) {
-                final RectF rectF = new RectF(rect);
-                mTmpMatrix.mapRect(rectF);
-                rectF.round(rect);
-            }
-            newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
-        }
-        return newBounds;
-    }
-}
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index ee3f4f4d..a45771e 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -19,7 +19,6 @@
 import android.graphics.Rect;
 import android.util.Size;
 import android.view.DisplayCutout;
-import android.view.Gravity;
 
 import java.util.Objects;
 
@@ -52,7 +51,7 @@
             return NO_CUTOUT;
         }
         final Size displaySize = new Size(displayWidth, displayHeight);
-        final Rect safeInsets = computeSafeInsets(displaySize, inner);
+        final Rect safeInsets = DisplayCutout.computeSafeInsets(displayWidth, displayHeight, inner);
         return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
     }
 
@@ -66,45 +65,6 @@
         return computeSafeInsets(mInner, width, height);
     }
 
-    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
-        if (displaySize.getWidth() == displaySize.getHeight()) {
-            throw new UnsupportedOperationException("not implemented: display=" + displaySize +
-                    " cutout=" + cutout);
-        }
-
-        int leftInset = Math.max(cutout.getWaterfallInsets().left,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
-        int topInset = Math.max(cutout.getWaterfallInsets().top,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
-        int rightInset = Math.max(cutout.getWaterfallInsets().right,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
-        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
-                findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
-                        Gravity.BOTTOM));
-
-        return new Rect(leftInset, topInset, rightInset, bottomInset);
-    }
-
-    private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
-        if (boundingRect.isEmpty()) {
-            return 0;
-        }
-
-        int inset = 0;
-        switch (gravity) {
-            case Gravity.TOP:
-                return Math.max(inset, boundingRect.bottom);
-            case Gravity.BOTTOM:
-                return Math.max(inset, display.getHeight() - boundingRect.top);
-            case Gravity.LEFT:
-                return Math.max(inset, boundingRect.right);
-            case Gravity.RIGHT:
-                return Math.max(inset, display.getWidth() - boundingRect.left);
-            default:
-                throw new IllegalArgumentException("unknown gravity: " + gravity);
-        }
-    }
-
     public DisplayCutout getDisplayCutout() {
         return mInner;
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4524bb7..df5fb28 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -277,6 +277,7 @@
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiLightsOut(bool lightsOut);
     void setPointerSpeed(int32_t speed);
+    void setPointerAcceleration(float acceleration);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setInteractive(bool interactive);
@@ -363,6 +364,9 @@
         // Pointer speed.
         int32_t pointerSpeed;
 
+        // Pointer acceleration.
+        float pointerAcceleration;
+
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled;
 
@@ -412,6 +416,7 @@
         AutoMutex _l(mLock);
         mLocked.systemUiLightsOut = false;
         mLocked.pointerSpeed = 0;
+        mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
         mLocked.pointerGesturesEnabled = true;
         mLocked.showTouches = false;
         mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
@@ -439,6 +444,7 @@
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
+        dump += StringPrintf(INDENT "Pointer Acceleration: %0.3f\n", mLocked.pointerAcceleration);
         dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
                 toString(mLocked.pointerGesturesEnabled));
         dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
@@ -628,6 +634,7 @@
 
         outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
                 * POINTER_SPEED_EXPONENT);
+        outConfig->pointerVelocityControlParameters.acceleration = mLocked.pointerAcceleration;
         outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
 
         outConfig->showTouches = mLocked.showTouches;
@@ -1066,6 +1073,22 @@
             InputReaderConfiguration::CHANGE_POINTER_SPEED);
 }
 
+void NativeInputManager::setPointerAcceleration(float acceleration) {
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mLocked.pointerAcceleration == acceleration) {
+            return;
+        }
+
+        ALOGI("Setting pointer acceleration to %0.3f", acceleration);
+        mLocked.pointerAcceleration = acceleration;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_POINTER_SPEED);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     { // acquire lock
         AutoMutex _l(mLock);
@@ -1586,6 +1609,13 @@
     return result;
 }
 
+static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr,
+                                           jint deviceId, jint locationKeyCode) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId,
+                                                                             locationKeyCode);
+}
+
 static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */,
                                        const std::shared_ptr<InputChannel>& inputChannel,
                                        void* data) {
@@ -1875,6 +1905,13 @@
     im->setPointerSpeed(speed);
 }
 
+static void nativeSetPointerAcceleration(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
+                                         jfloat acceleration) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setPointerAcceleration(acceleration);
+}
+
 static void nativeSetShowTouches(JNIEnv* /* env */,
         jclass /* clazz */, jlong ptr, jboolean enabled) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2337,6 +2374,7 @@
         {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
         {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
         {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
+        {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
         {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
          (void*)nativeCreateInputChannel},
         {"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;",
@@ -2365,6 +2403,7 @@
          (void*)nativeTransferTouchFocus},
         {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
         {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
+        {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
         {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
         {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
         {"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index b484796..f5e6c45 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -39,8 +39,8 @@
 
 static JavaVM* sJvm = nullptr;
 static jmethodID sMethodIdOnComplete;
-static jclass sFrequencyMappingClass;
-static jmethodID sFrequencyMappingCtor;
+static jclass sFrequencyProfileClass;
+static jmethodID sFrequencyProfileCtor;
 static struct {
     jmethodID setCapabilities;
     jmethodID setSupportedEffects;
@@ -51,7 +51,7 @@
     jmethodID setPrimitiveDelayMax;
     jmethodID setCompositionSizeMax;
     jmethodID setQFactor;
-    jmethodID setFrequencyMapping;
+    jmethodID setFrequencyProfile;
 } sVibratorInfoBuilderClassInfo;
 static struct {
     jfieldID id;
@@ -437,11 +437,11 @@
         env->SetFloatArrayRegion(maxAmplitudes, 0, amplitudes.size(),
                                  reinterpret_cast<jfloat*>(amplitudes.data()));
     }
-    jobject frequencyMapping =
-            env->NewObject(sFrequencyMappingClass, sFrequencyMappingCtor, resonantFrequency,
+    jobject frequencyProfile =
+            env->NewObject(sFrequencyProfileClass, sFrequencyProfileCtor, resonantFrequency,
                            minFrequency, frequencyResolution, maxAmplitudes);
-    env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyMapping,
-                          frequencyMapping);
+    env->CallObjectMethod(vibratorInfoBuilder, sVibratorInfoBuilderClassInfo.setFrequencyProfile,
+                          frequencyProfile);
 
     return info.isFailedLogged("vibratorGetInfo") ? JNI_FALSE : JNI_TRUE;
 }
@@ -485,9 +485,9 @@
     sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
     sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
 
-    jclass frequencyMappingClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyMapping");
-    sFrequencyMappingClass = static_cast<jclass>(env->NewGlobalRef(frequencyMappingClass));
-    sFrequencyMappingCtor = GetMethodIDOrDie(env, sFrequencyMappingClass, "<init>", "(FFF[F)V");
+    jclass frequencyProfileClass = FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfile");
+    sFrequencyProfileClass = static_cast<jclass>(env->NewGlobalRef(frequencyProfileClass));
+    sFrequencyProfileCtor = GetMethodIDOrDie(env, sFrequencyProfileClass, "<init>", "(FFF[F)V");
 
     jclass vibratorInfoBuilderClass = FindClassOrDie(env, "android/os/VibratorInfo$Builder");
     sVibratorInfoBuilderClassInfo.setCapabilities =
@@ -517,9 +517,9 @@
     sVibratorInfoBuilderClassInfo.setQFactor =
             GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setQFactor",
                              "(F)Landroid/os/VibratorInfo$Builder;");
-    sVibratorInfoBuilderClassInfo.setFrequencyMapping =
-            GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyMapping",
-                             "(Landroid/os/VibratorInfo$FrequencyMapping;)"
+    sVibratorInfoBuilderClassInfo.setFrequencyProfile =
+            GetMethodIDOrDie(env, vibratorInfoBuilderClass, "setFrequencyProfile",
+                             "(Landroid/os/VibratorInfo$FrequencyProfile;)"
                              "Landroid/os/VibratorInfo$Builder;");
 
     return jniRegisterNativeMethods(env,
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index baf2ede..574dbfd 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -38,7 +38,8 @@
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
-                <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/>
+                <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
+                            maxOccurs="1"/>
                 <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" />
                 <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">
                     <xs:annotation name="final"/>
@@ -67,6 +68,19 @@
                 <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonShort">
                     <xs:annotation name="final"/>
                 </xs:element>
+
+                <!-- Set of thresholds that dictate the change needed for screen brightness
+                adaptations -->
+                <xs:element type="thresholds" name="displayBrightnessChangeThresholds">
+                    <xs:annotation name="nonnull"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
+                <!-- Set of thresholds that dictate the change needed for ambient brightness
+                adaptations -->
+                <xs:element type="thresholds" name="ambientBrightnessChangeThresholds">
+                    <xs:annotation name="nonnull"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
             </xs:sequence>
         </xs:complexType>
     </xs:element>
@@ -81,7 +95,8 @@
 
     <xs:complexType name="highBrightnessMode">
         <xs:all>
-            <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1" maxOccurs="1">
+            <xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
+                        maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
             </xs:element>
@@ -110,7 +125,8 @@
 
     <xs:complexType name="hbmTiming">
         <xs:all>
-            <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1" maxOccurs="1">
+            <xs:element name="timeWindowSecs" type="xs:nonNegativeInteger" minOccurs="1"
+                        maxOccurs="1">
                 <xs:annotation name="nonnull"/>
                 <xs:annotation name="final"/>
             </xs:element>
@@ -216,5 +232,33 @@
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
+      </xs:complexType>
+
+    <!-- Thresholds for brightness changes. -->
+    <xs:complexType name="thresholds">
+        <xs:sequence>
+            <!-- Brightening thresholds. -->
+            <xs:element name="brighteningThresholds" type="brightnessThresholds" minOccurs="0"
+                        maxOccurs="1" >
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+            <!-- Darkening thresholds. -->
+            <xs:element name="darkeningThresholds" type="brightnessThresholds" minOccurs="0"
+                        maxOccurs="1" >
+                <xs:annotation name="nonnull"/>
+                <xs:annotation name="final"/>
+            </xs:element>
+        </xs:sequence>
     </xs:complexType>
+
+    <!-- Brightening and darkening minimum change thresholds. -->
+    <xs:complexType name="brightnessThresholds">
+        <!-- Minimum brightness change needed. -->
+        <xs:element name="minimum" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1" >
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6f97431..04f0916 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -1,6 +1,12 @@
 // Signature format: 2.0
 package com.android.server.display.config {
 
+  public class BrightnessThresholds {
+    ctor public BrightnessThresholds();
+    method @NonNull public final java.math.BigDecimal getMinimum();
+    method public final void setMinimum(@NonNull java.math.BigDecimal);
+  }
+
   public class Density {
     ctor public Density();
     method @NonNull public final java.math.BigInteger getDensity();
@@ -18,9 +24,11 @@
 
   public class DisplayConfiguration {
     ctor public DisplayConfiguration();
+    method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
     method public final java.math.BigInteger getAmbientLightHorizonLong();
     method public final java.math.BigInteger getAmbientLightHorizonShort();
     method @Nullable public final com.android.server.display.config.DensityMap getDensityMap();
+    method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public final com.android.server.display.config.SensorDetails getProxSensor();
@@ -31,9 +39,11 @@
     method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease();
     method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease();
+    method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public final void setAmbientLightHorizonLong(java.math.BigInteger);
     method public final void setAmbientLightHorizonShort(java.math.BigInteger);
     method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap);
+    method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public final void setProxSensor(com.android.server.display.config.SensorDetails);
@@ -121,6 +131,14 @@
     enum_constant public static final com.android.server.display.config.ThermalStatus shutdown;
   }
 
+  public class Thresholds {
+    ctor public Thresholds();
+    method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds();
+    method @NonNull public final com.android.server.display.config.BrightnessThresholds getDarkeningThresholds();
+    method public final void setBrighteningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+    method public final void setDarkeningThresholds(@NonNull com.android.server.display.config.BrightnessThresholds);
+  }
+
   public class XmlParser {
     ctor public XmlParser();
     method public static com.android.server.display.config.DisplayConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index f19202a..2090ab3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -32,6 +32,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -306,6 +307,8 @@
     public boolean mAdminCanGrantSensorsPermissions;
     public boolean mPreferentialNetworkServiceEnabled =
             DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+    public PreferentialNetworkServiceConfig mPreferentialNetworkServiceConfig =
+            PreferentialNetworkServiceConfig.DEFAULT;
 
     private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true;
     boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 9b87b9d..200b120 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -171,11 +171,11 @@
     public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){}
 
     @Override
-    public void resetDrawables(@NonNull int[] drawableIds){}
+    public void resetDrawables(@NonNull String[] drawableIds){}
 
     @Override
     public ParcelableResource getDrawable(
-            int drawableId, int drawableStyle, int drawableSource) {
+            String drawableId, String drawableStyle, String drawableSource) {
         return null;
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
index 9a98235..e70c071 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -16,10 +16,10 @@
 
 package com.android.server.devicepolicy;
 
-import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES;
-import static android.app.admin.DevicePolicyResources.Drawable.Style;
-import static android.app.admin.DevicePolicyResources.Drawable.Style.UPDATABLE_DRAWABLE_STYLES;
-import static android.app.admin.DevicePolicyResources.Drawable.UPDATABLE_DRAWABLE_IDS;
+import static android.app.admin.DevicePolicyResources.Drawables.Source.UPDATABLE_DRAWABLE_SOURCES;
+import static android.app.admin.DevicePolicyResources.Drawables.Style;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.UPDATABLE_DRAWABLE_STYLES;
+import static android.app.admin.DevicePolicyResources.Drawables.UPDATABLE_DRAWABLE_IDS;
 import static android.app.admin.DevicePolicyResources.Strings.UPDATABLE_STRING_IDS;
 
 import static java.util.Objects.requireNonNull;
@@ -27,7 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyDrawableResource;
-import android.app.admin.DevicePolicyResources;
+import android.app.admin.DevicePolicyResources.Drawables;
 import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.ParcelableResource;
 import android.os.Environment;
@@ -72,13 +72,13 @@
     /**
      * Map of <drawable_id, <style_id, resource_value>>
      */
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForStyle = new HashMap<>();
 
     /**
      * Map of <drawable_id, <source_id, resource_value>>
      */
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForSource = new HashMap<>();
 
     /**
@@ -103,14 +103,17 @@
     boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
         boolean updated = false;
         for (int i = 0; i < drawables.size(); i++) {
-            int drawableId = drawables.get(i).getDrawableId();
-            int drawableStyle = drawables.get(i).getDrawableStyle();
-            int drawableSource = drawables.get(i).getDrawableSource();
+            String drawableId = drawables.get(i).getDrawableId();
+            String drawableStyle = drawables.get(i).getDrawableStyle();
+            String drawableSource = drawables.get(i).getDrawableSource();
             ParcelableResource resource = drawables.get(i).getResource();
 
+            Objects.requireNonNull(drawableId, "drawableId must be provided.");
+            Objects.requireNonNull(drawableStyle, "drawableStyle must be provided.");
+            Objects.requireNonNull(drawableSource, "drawableSource must be provided.");
             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
 
-            if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) {
+            if (Drawables.Source.UNDEFINED.equals(drawableSource)) {
                 updated |= updateDrawable(drawableId, drawableStyle, resource);
             } else {
                 updated |= updateDrawableForSource(drawableId, drawableSource, resource);
@@ -126,7 +129,7 @@
     }
 
     private boolean updateDrawable(
-            int drawableId, int drawableStyle, ParcelableResource updatableResource) {
+            String drawableId, String drawableStyle, ParcelableResource updatableResource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
@@ -149,7 +152,7 @@
 
     // TODO(b/214576716): change this to respect style
     private boolean updateDrawableForSource(
-            int drawableId, int drawableSource, ParcelableResource updatableResource) {
+            String drawableId, String drawableSource, ParcelableResource updatableResource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
@@ -173,11 +176,11 @@
     /**
      * Returns {@code false} if no resources were removed.
      */
-    boolean removeDrawables(@NonNull int[] drawableIds) {
+    boolean removeDrawables(@NonNull String[] drawableIds) {
         synchronized (mLock) {
             boolean removed = false;
             for (int i = 0; i < drawableIds.length; i++) {
-                int drawableId = drawableIds[i];
+                String drawableId = drawableIds[i];
                 removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null
                         || mUpdatedDrawablesForSource.remove(drawableId) != null;
             }
@@ -191,7 +194,7 @@
 
     @Nullable
     ParcelableResource getDrawable(
-            int drawableId, int drawableStyle, int drawableSource) {
+            String drawableId, String drawableStyle, String drawableSource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
             Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId);
         }
@@ -231,7 +234,9 @@
             String stringId = strings.get(i).getStringId();
             ParcelableResource resource = strings.get(i).getResource();
 
+            Objects.requireNonNull(stringId, "stringId must be provided.");
             Objects.requireNonNull(resource, "ParcelableResource must be provided.");
+
             updated |= updateString(stringId, resource);
         }
         if (!updated) {
@@ -392,19 +397,19 @@
 
         void writeInner(TypedXmlSerializer out) throws IOException {
             if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) {
-                for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+                for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
                         : mUpdatedDrawablesForStyle.entrySet()) {
                     out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY);
-                    out.attributeInt(
+                    out.attribute(
                             /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
                     out.attributeInt(
                             /* namespace= */ null,
                             ATTR_DRAWABLE_STYLE_SIZE,
                             drawableEntry.getValue().size());
                     int counter = 0;
-                    for (Map.Entry<Integer, ParcelableResource> styleEntry
+                    for (Map.Entry<String, ParcelableResource> styleEntry
                             : drawableEntry.getValue().entrySet()) {
-                        out.attributeInt(
+                        out.attribute(
                                 /* namespace= */ null,
                                 ATTR_DRAWABLE_STYLE + (counter++),
                                 styleEntry.getKey());
@@ -414,19 +419,19 @@
                 }
             }
             if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) {
-                for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry
+                for (Map.Entry<String, Map<String, ParcelableResource>> drawableEntry
                         : mUpdatedDrawablesForSource.entrySet()) {
                     out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
-                    out.attributeInt(
+                    out.attribute(
                             /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey());
                     out.attributeInt(
                             /* namespace= */ null,
                             ATTR_DRAWABLE_SOURCE_SIZE,
                             drawableEntry.getValue().size());
                     int counter = 0;
-                    for (Map.Entry<Integer, ParcelableResource> sourceEntry
+                    for (Map.Entry<String, ParcelableResource> sourceEntry
                             : drawableEntry.getValue().entrySet()) {
-                        out.attributeInt(
+                        out.attribute(
                                 /* namespace= */ null,
                                 ATTR_DRAWABLE_SOURCE + (counter++),
                                 sourceEntry.getKey());
@@ -457,7 +462,7 @@
             }
             switch (tag) {
                 case TAG_DRAWABLE_STYLE_ENTRY:
-                    int drawableId = parser.getAttributeInt(
+                    String drawableId = parser.getAttributeValue(
                             /* namespace= */ null, ATTR_DRAWABLE_ID);
                     mUpdatedDrawablesForStyle.put(
                             drawableId,
@@ -465,7 +470,7 @@
                     int size = parser.getAttributeInt(
                             /* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE);
                     for (int i = 0; i < size; i++) {
-                        int style = parser.getAttributeInt(
+                        String style = parser.getAttributeValue(
                                 /* namespace= */ null, ATTR_DRAWABLE_STYLE + i);
                         mUpdatedDrawablesForStyle.get(drawableId).put(
                                 style,
@@ -473,13 +478,13 @@
                     }
                     break;
                 case TAG_DRAWABLE_SOURCE_ENTRY:
-                    drawableId = parser.getAttributeInt(
+                    drawableId = parser.getAttributeValue(
                             /* namespace= */ null, ATTR_DRAWABLE_ID);
                     mUpdatedDrawablesForSource.put(drawableId, new HashMap<>());
                     size = parser.getAttributeInt(
                             /* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE);
                     for (int i = 0; i < size; i++) {
-                        int source = parser.getAttributeInt(
+                        String source = parser.getAttributeValue(
                                 /* namespace= */ null, ATTR_DRAWABLE_SOURCE + i);
                         mUpdatedDrawablesForSource.get(drawableId).put(
                                 source,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 92952cd..e0b6273 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -125,6 +125,8 @@
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -198,6 +200,7 @@
 import android.app.admin.ParcelableResource;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.app.admin.StartInstallingUpdateCallback;
@@ -244,6 +247,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.hardware.usb.UsbManager;
+import android.location.Location;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -259,6 +263,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
@@ -324,6 +329,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.LocalePicker;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.NetworkUtilsInternal;
@@ -403,6 +409,8 @@
 
     protected static final String LOG_TAG = "DevicePolicyManager";
 
+    private static final String ATTRIBUTION_TAG = "DevicePolicyManagerService";
+
     static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE
 
     static final String DEVICE_POLICIES_XML = "device_policies.xml";
@@ -1771,7 +1779,7 @@
      * Instantiates the service.
      */
     public DevicePolicyManagerService(Context context) {
-        this(new Injector(context));
+        this(new Injector(context.createAttributionContext(ATTRIBUTION_TAG)));
     }
 
     @VisibleForTesting
@@ -3356,14 +3364,14 @@
         updatePermissionPolicyCache(userId);
         updateAdminCanGrantSensorsPermissionCache(userId);
 
-        final boolean preferentialNetworkServiceEnabled;
+        final PreferentialNetworkServiceConfig preferentialNetworkServiceConfig;
         synchronized (getLockObject()) {
             ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
-            preferentialNetworkServiceEnabled = owner != null
-                    ? owner.mPreferentialNetworkServiceEnabled
-                             : DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
+            preferentialNetworkServiceConfig = owner != null
+                    ? owner.mPreferentialNetworkServiceConfig
+                    : PreferentialNetworkServiceConfig.DEFAULT;
         }
-        updateNetworkPreferenceForUser(userId, preferentialNetworkServiceEnabled);
+        updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfig);
 
         startOwnerService(userId, "start-user");
     }
@@ -3380,7 +3388,7 @@
 
     @Override
     void handleStopUser(int userId) {
-        updateNetworkPreferenceForUser(userId, false);
+        updateNetworkPreferenceForUser(userId, PreferentialNetworkServiceConfig.DEFAULT);
         stopOwnerService(userId, "stop-user");
     }
 
@@ -7208,6 +7216,64 @@
         return getFrpManagementAgentUid() != -1;
     }
 
+    @Override
+    public void sendLostModeLocationUpdate(AndroidFuture<Boolean> future) {
+        if (!mHasFeature) {
+            future.complete(false);
+            return;
+        }
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.SEND_LOST_MODE_LOCATION_UPDATES));
+
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            Preconditions.checkState(admin != null,
+                    "Lost mode location updates can only be sent on an organization-owned device.");
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                final List<String> providers =
+                        mInjector.getLocationManager().getAllProviders().stream()
+                                .filter(mInjector.getLocationManager()::isProviderEnabled)
+                                .collect(Collectors.toList());
+                if (providers.isEmpty()) {
+                    future.complete(false);
+                    return;
+                }
+
+                final CancellationSignal cancellationSignal = new CancellationSignal();
+                List<String> providersWithNullLocation = new ArrayList<String>();
+                for (String provider : providers) {
+                    mInjector.getLocationManager().getCurrentLocation(provider, cancellationSignal,
+                            mContext.getMainExecutor(), location -> {
+                                if (cancellationSignal.isCanceled()) {
+                                    return;
+                                } else if (location != null) {
+                                    sendLostModeLocationUpdate(admin, location);
+                                    cancellationSignal.cancel();
+                                    future.complete(true);
+                                } else {
+                                    // location == null, provider wasn't able to get location, see
+                                    // if there are more providers
+                                    providersWithNullLocation.add(provider);
+                                    if (providers.size() == providersWithNullLocation.size()) {
+                                        future.complete(false);
+                                    }
+                                }
+                            }
+                    );
+                }
+            });
+        }
+    }
+
+    private void sendLostModeLocationUpdate(ActiveAdmin admin, Location location) {
+        final Intent intent = new Intent(
+                DevicePolicyManager.ACTION_LOST_MODE_LOCATION_UPDATE);
+        intent.putExtra(DevicePolicyManager.EXTRA_LOST_MODE_LOCATION, location);
+        intent.setPackage(admin.info.getPackageName());
+        mContext.sendBroadcastAsUser(intent, admin.getUserHandle());
+    }
+
     /**
      * Called by a privileged caller holding {@code BIND_DEVICE_ADMIN} permission to retrieve
      * the remove warning for the given device admin.
@@ -12104,7 +12170,7 @@
         final CallerIdentity caller = getCallerIdentity();
         Preconditions.checkCallAuthorization(isProfileOwner(caller),
                 "Caller is not profile owner;"
-                        + " only profile owner may control the preferntial network service");
+                        + " only profile owner may control the preferential network service");
         synchronized (getLockObject()) {
             final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
                     caller.getUserId());
@@ -12141,6 +12207,47 @@
     }
 
     @Override
+    public void setPreferentialNetworkServiceConfig(
+            PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        if (!mHasFeature) {
+            return;
+        }
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner;"
+                        + " only profile owner may control the preferential network service");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(
+                    caller.getUserId());
+            if (!requiredAdmin.mPreferentialNetworkServiceConfig.equals(
+                    preferentialNetworkServiceConfig)) {
+                requiredAdmin.mPreferentialNetworkServiceConfig = preferentialNetworkServiceConfig;
+                saveSettingsLocked(caller.getUserId());
+            }
+        }
+        updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfig);
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
+                .setBoolean(preferentialNetworkServiceConfig.isEnabled())
+                .write();
+    }
+
+    @Override
+    public PreferentialNetworkServiceConfig getPreferentialNetworkServiceConfig() {
+        if (!mHasFeature) {
+            return PreferentialNetworkServiceConfig.DEFAULT;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(isProfileOwner(caller),
+                "Caller is not profile owner");
+        synchronized (getLockObject()) {
+            final ActiveAdmin requiredAdmin = getProfileOwnerAdminLocked(caller.getUserId());
+            return requiredAdmin.mPreferentialNetworkServiceConfig;
+        }
+    }
+
+    @Override
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -17963,11 +18070,48 @@
         if (!isManagedProfile(userId)) {
             return;
         }
-        int networkPreference = preferentialNetworkServiceEnabled
-                ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT;
         ProfileNetworkPreference.Builder preferenceBuilder =
                 new ProfileNetworkPreference.Builder();
-        preferenceBuilder.setPreference(networkPreference);
+        if (preferentialNetworkServiceEnabled) {
+            preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+            preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        } else {
+            preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+        }
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceBuilder.build());
+        mInjector.binderWithCleanCallingIdentity(() ->
+                mInjector.getConnectivityManager().setProfileNetworkPreferences(
+                        UserHandle.of(userId), preferences,
+                        null /* executor */, null /* listener */));
+    }
+
+    private void updateNetworkPreferenceForUser(int userId,
+            PreferentialNetworkServiceConfig preferentialNetworkServiceConfig) {
+        if (!isManagedProfile(userId)) {
+            return;
+        }
+        ProfileNetworkPreference.Builder preferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        if (preferentialNetworkServiceConfig.isEnabled()) {
+            if (preferentialNetworkServiceConfig.isFallbackToDefaultConnectionAllowed()) {
+                preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+            } else {
+                preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+            }
+        } else {
+            preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+        }
+        List<Integer> allowedUids = Arrays.stream(
+                preferentialNetworkServiceConfig.getIncludedUids()).boxed().collect(
+                        Collectors.toList());
+        List<Integer> excludedUids = Arrays.stream(
+                preferentialNetworkServiceConfig.getExcludedUids()).boxed().collect(
+                Collectors.toList());
+        preferenceBuilder.setIncludedUids(allowedUids);
+        preferenceBuilder.setExcludedUids(excludedUids);
+        preferenceBuilder.setPreferenceEnterpriseId(
+                preferentialNetworkServiceConfig.getNetworkId());
         List<ProfileNetworkPreference> preferences = new ArrayList<>();
         preferences.add(preferenceBuilder.build());
         mInjector.binderWithCleanCallingIdentity(() ->
@@ -18215,13 +18359,13 @@
         mInjector.binderWithCleanCallingIdentity(() -> {
             if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) {
                 sendDrawableUpdatedBroadcast(
-                        drawables.stream().mapToInt(d -> d.getDrawableId()).toArray());
+                        drawables.stream().map(s -> s.getDrawableId()).toArray(String[]::new));
             }
         });
     }
 
     @Override
-    public void resetDrawables(@NonNull int[] drawableIds) {
+    public void resetDrawables(@NonNull String[] drawableIds) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
                 android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
 
@@ -18235,24 +18379,15 @@
     }
 
     @Override
-    public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) {
+    public ParcelableResource getDrawable(
+            String drawableId, String drawableStyle, String drawableSource) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mDeviceManagementResourcesProvider.getDrawable(
                         drawableId, drawableStyle, drawableSource));
     }
 
-    private void sendDrawableUpdatedBroadcast(int[] drawableIds) {
-        final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-        intent.putExtra(EXTRA_RESOURCE_ID, drawableIds);
-        intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* value= */ true);
-        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
-        List<UserInfo> users = mUserManager.getAliveUsers();
-        for (int i = 0; i < users.size(); i++) {
-            UserHandle user = users.get(i).getUserHandle();
-            mContext.sendBroadcastAsUser(intent, user);
-        }
+    private void sendDrawableUpdatedBroadcast(String[] drawableIds) {
+        sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_DRAWABLE, drawableIds);
     }
 
     @Override
@@ -18288,9 +18423,13 @@
     }
 
     private void sendStringsUpdatedBroadcast(String[] stringIds) {
+        sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_STRING, stringIds);
+    }
+
+    private void sendResourceUpdatedBroadcast(String resourceType, String[] resourceIds) {
         final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
-        intent.putExtra(EXTRA_RESOURCE_ID, stringIds);
-        intent.putExtra(EXTRA_RESOURCE_TYPE_STRING, /* value= */ true);
+        intent.putExtra(EXTRA_RESOURCE_ID, resourceIds);
+        intent.putExtra(resourceType, /* value= */ true);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ad8753d..1fe71f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -138,6 +138,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.locales.LocaleManagerService;
 import com.android.server.location.LocationManagerService;
+import com.android.server.logcat.LogcatManagerService;
 import com.android.server.media.MediaRouterService;
 import com.android.server.media.metrics.MediaMetricsManagerService;
 import com.android.server.media.projection.MediaProjectionManagerService;
@@ -1631,6 +1632,10 @@
             mSystemServiceManager.startService(AppIntegrityManagerService.class);
             t.traceEnd();
 
+            t.traceBegin("StartLogcatManager");
+            mSystemServiceManager.startService(LogcatManagerService.class);
+            t.traceEnd();
+
         } catch (Throwable e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 6e72479..d562786 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -864,7 +864,15 @@
         if (device == null) {
             throw new IllegalArgumentException("no such device for " + deviceInfo);
         }
-        return device.getDeviceStatus();
+        int uid = Binder.getCallingUid();
+        if (device.isUidAllowed(uid)) {
+            return device.getDeviceStatus();
+        } else {
+            Log.e(TAG, "getDeviceStatus() invalid UID = " + uid);
+            EventLog.writeEvent(0x534e4554, "203549963",
+                    uid, "getDeviceStatus: invalid uid");
+            return null;
+        }
     }
 
     @Override
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
index 1d4c94d..99b0f25 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.selectiontoolbar;
 
+import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService;
+
 import com.android.server.infra.ServiceNameResolver;
 
 import java.io.PrintWriter;
@@ -24,7 +26,7 @@
 
     // TODO: move to SysUi or ExtServices
     private static final String SELECTION_TOOLBAR_SERVICE_NAME =
-            "android/com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService";
+            "android/" + DefaultSelectionToolbarRenderService.class.getName();
 
     @Override
     public String getDefaultServiceName(int userId) {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index 57562ef..f266e76 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -31,9 +31,9 @@
     override val creator = ParsedPermissionGroupImpl.CREATOR
 
     override val subclassBaseParams = listOf(
-        ParsedPermissionGroup::getRequestDetailResourceId,
-        ParsedPermissionGroup::getBackgroundRequestDetailResourceId,
-        ParsedPermissionGroup::getBackgroundRequestResourceId,
+        ParsedPermissionGroup::getRequestDetailRes,
+        ParsedPermissionGroup::getBackgroundRequestDetailRes,
+        ParsedPermissionGroup::getBackgroundRequestRes,
         ParsedPermissionGroup::getRequestRes,
         ParsedPermissionGroup::getPriority,
     )
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 037da24..0302d57 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -43,10 +43,9 @@
     override fun mainComponentSubclassExtraParams() = listOf(
         getSetByValue(
             ParsedProvider::getUriPermissionPatterns,
-            ParsedProviderImpl::setUriPermissionPatterns,
+            ParsedProviderImpl::addUriPermissionPattern,
             PatternMatcher("testPattern", PatternMatcher.PATTERN_LITERAL),
-            transformGet = { it?.singleOrNull() },
-            transformSet = { arrayOf(it) },
+            transformGet = { it.singleOrNull() },
             compare = { first, second ->
                 equalBy(
                     first, second,
@@ -57,15 +56,14 @@
         ),
         getSetByValue(
             ParsedProvider::getPathPermissions,
-            ParsedProviderImpl::setPathPermissions,
+            ParsedProviderImpl::addPathPermission,
             PathPermission(
                 "testPermissionPattern",
                 PatternMatcher.PATTERN_LITERAL,
                 "test.READ_PERMISSION",
                 "test.WRITE_PERMISSION"
             ),
-            transformGet = { it?.singleOrNull() },
-            transformSet = { arrayOf(it) },
+            transformGet = { it.singleOrNull() },
             compare = { first, second ->
                 equalBy(
                     first, second,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index cfae9a3..153ce17 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server.job.controllers;
 
+import static android.app.job.JobInfo.PRIORITY_DEFAULT;
+import static android.app.job.JobInfo.PRIORITY_HIGH;
+import static android.app.job.JobInfo.PRIORITY_LOW;
+import static android.app.job.JobInfo.PRIORITY_MIN;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -269,14 +274,14 @@
     }
 
     private void setCharging() {
-        doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+        when(mJobSchedulerService.isBatteryCharging()).thenReturn(true);
         synchronized (mQuotaController.mLock) {
             mQuotaController.onBatteryStateChangedLocked();
         }
     }
 
     private void setDischarging() {
-        doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+        when(mJobSchedulerService.isBatteryCharging()).thenReturn(false);
         synchronized (mQuotaController.mLock) {
             mQuotaController.onBatteryStateChangedLocked();
         }
@@ -407,6 +412,14 @@
         }
     }
 
+    private void setDeviceConfigFloat(String key, float val) {
+        mDeviceConfigPropertiesBuilder.setFloat(key, val);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.prepareForUpdatedConstantsLocked();
+            mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+        }
+    }
+
     private void waitForNonDelayedMessagesProcessed() {
         mQuotaController.getHandler().runWithScissors(() -> {}, 15_000);
     }
@@ -839,7 +852,7 @@
                         SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
                 assertEquals(expectedStats, inputStats);
                 assertTrue(mQuotaController.isWithinQuotaLocked(
-                        SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+                        SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
             }
             assertTrue("Job not ready: " + jobStatus, jobStatus.isReady());
         }
@@ -863,7 +876,7 @@
             assertEquals(expectedStats, inputStats);
             assertFalse(
                     mQuotaController.isWithinQuotaLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
         }
 
         // Quota should be exceeded due to activity in active timer.
@@ -888,7 +901,7 @@
             assertEquals(expectedStats, inputStats);
             assertFalse(
                     mQuotaController.isWithinQuotaLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
             assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady());
         }
     }
@@ -1484,7 +1497,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         setStandbyBucket(FREQUENT_INDEX);
@@ -1494,7 +1507,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         setStandbyBucket(WORKING_INDEX);
@@ -1504,7 +1517,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(7 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
@@ -1516,7 +1529,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
     }
 
@@ -1540,7 +1553,7 @@
             // Max time will phase out, so should use bucket limit.
             assertEquals(10 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1556,7 +1569,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(10 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1573,7 +1586,7 @@
                             SOURCE_USER_ID, SOURCE_PACKAGE));
             assertEquals(3 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
     }
 
@@ -1606,7 +1619,7 @@
             // window time.
             assertEquals(10 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
-                            SOURCE_USER_ID, SOURCE_PACKAGE));
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
         }
 
         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1633,15 +1646,115 @@
             // Max time only has one minute phase out. Bucket time has 2 minute phase out.
             assertEquals(9 * MINUTE_IN_MILLIS,
                     mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+        }
+    }
+
+    /**
+     * Test getTimeUntilQuotaConsumedLocked when the determination is based on the job's priority.
+     */
+    @Test
+    public void testGetTimeUntilQuotaConsumedLocked_Priority() {
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        // Close to RARE boundary.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
+                        150 * SECOND_IN_MILLIS, 5), false);
+        // Far away from FREQUENT boundary.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (7 * HOUR_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false);
+        // Overlap WORKING_SET boundary.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+                        2 * MINUTE_IN_MILLIS, 5), false);
+        // Close to ACTIVE boundary.
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+        setStandbyBucket(RARE_INDEX);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(30 * SECOND_IN_MILLIS,
+                    mQuotaController.getRemainingExecutionTimeLocked(
                             SOURCE_USER_ID, SOURCE_PACKAGE));
+            assertEquals(3 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+            assertEquals(3 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+            assertEquals(0,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+            assertEquals(0,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+        }
+
+        setStandbyBucket(FREQUENT_INDEX);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(3 * MINUTE_IN_MILLIS,
+                    mQuotaController.getRemainingExecutionTimeLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE));
+            assertEquals(3 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+            assertEquals(3 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+            assertEquals(30 * SECOND_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+            assertEquals(0,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+        }
+
+        setStandbyBucket(WORKING_INDEX);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(6 * MINUTE_IN_MILLIS,
+                    mQuotaController.getRemainingExecutionTimeLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE));
+            assertEquals(7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+            assertEquals(7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+            assertEquals(4 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+            assertEquals(2 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+        }
+
+        // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
+        // max execution time.
+        setStandbyBucket(ACTIVE_INDEX);
+        synchronized (mQuotaController.mLock) {
+            assertEquals(7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getRemainingExecutionTimeLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE));
+            assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+            assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+            assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+            assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+                    mQuotaController.getTimeUntilQuotaConsumedLocked(
+                            SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
         }
     }
 
     @Test
     public void testIsWithinQuotaLocked_NeverApp() {
         synchronized (mQuotaController.mLock) {
-            assertFalse(
-                    mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test.never", NEVER_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1649,7 +1762,8 @@
     public void testIsWithinQuotaLocked_Charging() {
         setCharging();
         synchronized (mQuotaController.mLock) {
-            assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1663,7 +1777,8 @@
                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
         synchronized (mQuotaController.mLock) {
             mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
-            assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1680,7 +1795,7 @@
         synchronized (mQuotaController.mLock) {
             mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount);
             assertFalse(mQuotaController.isWithinQuotaLocked(
-                    0, "com.android.test.spam", WORKING_INDEX));
+                    0, "com.android.test.spam", WORKING_INDEX, PRIORITY_DEFAULT));
         }
 
         mQuotaController.saveTimingSession(0, "com.android.test.frequent",
@@ -1690,7 +1805,7 @@
                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false);
         synchronized (mQuotaController.mLock) {
             assertFalse(mQuotaController.isWithinQuotaLocked(
-                    0, "com.android.test.frequent", FREQUENT_INDEX));
+                    0, "com.android.test.frequent", FREQUENT_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1706,7 +1821,8 @@
                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false);
         synchronized (mQuotaController.mLock) {
             mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
-            assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1722,7 +1838,8 @@
                 false);
         synchronized (mQuotaController.mLock) {
             mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount);
-            assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
         }
     }
 
@@ -1875,22 +1992,66 @@
 
                 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
                         i < 2,
-                        mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+                        mQuotaController.isWithinQuotaLocked(
+                                0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
                 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
                         i < 3,
                         mQuotaController.isWithinQuotaLocked(
-                                0, "com.android.test", FREQUENT_INDEX));
+                                0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
                 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
                         i < 4,
-                        mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+                        mQuotaController.isWithinQuotaLocked(
+                                0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
                 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
                         i < 5,
-                        mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX));
+                        mQuotaController.isWithinQuotaLocked(
+                                0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
             }
         }
     }
 
     @Test
+    public void testIsWithinQuotaLocked_Priority() {
+        setDischarging();
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", FREQUENT_INDEX, PRIORITY_HIGH));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", FREQUENT_INDEX, PRIORITY_LOW));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", FREQUENT_INDEX, PRIORITY_MIN));
+
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_HIGH));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_LOW));
+            assertFalse(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", WORKING_INDEX, PRIORITY_MIN));
+
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", ACTIVE_INDEX, PRIORITY_HIGH));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", ACTIVE_INDEX, PRIORITY_LOW));
+            assertTrue(mQuotaController.isWithinQuotaLocked(
+                    0, "com.android.test", ACTIVE_INDEX, PRIORITY_MIN));
+        }
+    }
+
+    @Test
     public void testIsWithinEJQuotaLocked_NeverApp() {
         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
         setStandbyBucket(NEVER_INDEX, js);
@@ -2116,6 +2277,12 @@
         final int standbyBucket = ACTIVE_INDEX;
         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+        setStandbyBucket(standbyBucket, jobStatus);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+        }
+
         // No sessions saved yet.
         synchronized (mQuotaController.mLock) {
             mQuotaController.maybeScheduleStartAlarmLocked(
@@ -2150,10 +2317,7 @@
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
-        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
-        setStandbyBucket(standbyBucket, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
             mQuotaController.prepareForExecutionLocked(jobStatus);
         }
         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
@@ -2179,19 +2343,24 @@
         // Working set window size is 2 hours.
         final int standbyBucket = WORKING_INDEX;
 
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1);
+        setStandbyBucket(standbyBucket, jobStatus);
         synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
             // No sessions saved yet.
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2201,37 +2370,41 @@
         // Counting backwards, the quota will come back one minute before the end.
         final long expectedAlarmTime =
                 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2244,22 +2417,29 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null);
+        }
+
         // Frequent window size is 8 hours.
         final int standbyBucket = FREQUENT_INDEX;
 
         // No sessions saved yet.
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2267,37 +2447,41 @@
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2314,6 +2498,11 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null);
+        }
+
         // The app is really in the NEVER bucket but is elevated somehow (eg via uidActive).
         setStandbyBucket(NEVER_INDEX);
         final int effectiveStandbyBucket = FREQUENT_INDEX;
@@ -2390,22 +2579,30 @@
         // Rare window size is 24 hours.
         final int standbyBucket = RARE_INDEX;
 
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1);
+        setStandbyBucket(standbyBucket, jobStatus);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+        }
+
         // Prevent timing session throttling from affecting the test.
         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50);
 
         // No sessions saved yet.
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2417,42 +2614,168 @@
         final long expectedAlarmTime =
                 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
                         + mQcConstants.IN_QUOTA_BUFFER_MS;
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Add some more sessions, but still in quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Test when out of quota.
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_Priority() {
+        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaController);
+        doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+        setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 5);
+
+        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (24 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        JobStatus jobDef = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+                SOURCE_PACKAGE, CALLING_UID,
+                new JobInfo.Builder(1, new ComponentName(mContext, "TestQuotaJobService"))
+                        .setPriority(PRIORITY_DEFAULT)
+                        .build());
+        JobStatus jobLow = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+                SOURCE_PACKAGE, CALLING_UID,
+                new JobInfo.Builder(2, new ComponentName(mContext, "TestQuotaJobService"))
+                        .setPriority(PRIORITY_LOW)
+                        .build());
+        JobStatus jobMin = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+                SOURCE_PACKAGE, CALLING_UID,
+                new JobInfo.Builder(3, new ComponentName(mContext, "TestQuotaJobService"))
+                        .setPriority(PRIORITY_MIN)
+                        .build());
+
+        setStandbyBucket(RARE_INDEX, jobDef, jobLow, jobMin);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+            // Min job requires 5 mins of surplus.
+            long expectedAlarmTime = now + 23 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+            // Low job requires 2.5 mins of surplus.
+            expectedAlarmTime = now + 17 * HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+            // Default+ jobs require IN_QUOTA_BUFFER_MS.
+            expectedAlarmTime = now + mQcConstants.IN_QUOTA_BUFFER_MS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+            setStandbyBucket(FREQUENT_INDEX, jobDef, jobLow, jobMin);
+
+            mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+            // Min job requires 5 mins of surplus.
+            expectedAlarmTime = now + 7 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+            // Low job requires 2.5 mins of surplus.
+            expectedAlarmTime = now + HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+            // Default+ jobs already have enough quota.
+            inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                    anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+            setStandbyBucket(WORKING_INDEX, jobDef, jobLow, jobMin);
+
+            mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+            // Min job requires 5 mins of surplus.
+            expectedAlarmTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+            inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+                    anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+            // Low job has enough surplus.
+            inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                    anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+            mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+            // Default+ jobs already have enough quota.
+            inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+                    anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        }
+    }
+
     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
     @Test
     public void testMaybeScheduleStartAlarmLocked_BucketChange() {
@@ -2464,24 +2787,29 @@
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
 
         // Affects rare bucket
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false);
         // Affects frequent and rare buckets
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false);
         // Affects working, frequent, and rare buckets
         final long outOfQuotaTime = now - HOUR_IN_MILLIS;
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false);
         // Affects all buckets
-        mQuotaController.saveTimingSession(0, "com.android.test",
+        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false);
 
         InOrder inOrder = inOrder(mAlarmManager);
 
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1);
+
         // Start in ACTIVE bucket.
+        setStandbyBucket(ACTIVE_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2492,8 +2820,10 @@
         final long expectedWorkingAlarmTime =
                 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
                         + mQcConstants.IN_QUOTA_BUFFER_MS;
+        setStandbyBucket(WORKING_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
@@ -2502,8 +2832,10 @@
         final long expectedFrequentAlarmTime =
                 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
                         + mQcConstants.IN_QUOTA_BUFFER_MS;
+        setStandbyBucket(FREQUENT_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
@@ -2512,29 +2844,37 @@
         final long expectedRareAlarmTime =
                 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
                         + mQcConstants.IN_QUOTA_BUFFER_MS;
+        setStandbyBucket(RARE_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // And back up again.
+        setStandbyBucket(FREQUENT_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
                 eq(TAG_QUOTA_CHECK), any(), any());
 
+        setStandbyBucket(WORKING_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
                 eq(TAG_QUOTA_CHECK), any(), any());
 
+        setStandbyBucket(ACTIVE_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+            mQuotaController.maybeScheduleStartAlarmLocked(
+                    SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2551,6 +2891,13 @@
 
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final int standbyBucket = WORKING_INDEX;
+
+        JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
+        setStandbyBucket(standbyBucket, jobStatus);
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+        }
+
         ExecutionStats stats;
         synchronized (mQuotaController.mLock) {
             stats = mQuotaController.getExecutionStatsLocked(
@@ -2646,6 +2993,11 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+        }
+
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Working set window size is 2 hours.
         final int standbyBucket = WORKING_INDEX;
@@ -2671,13 +3023,17 @@
                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
-
     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+        }
+
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Working set window size is 2 hours.
         final int standbyBucket = WORKING_INDEX;
@@ -2708,6 +3064,8 @@
     public void testConstantsUpdating_ValidValues() {
         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
@@ -2748,6 +3106,8 @@
 
         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
         assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
+        assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+        assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(45 * MINUTE_IN_MILLIS,
@@ -2793,6 +3153,8 @@
         // Test negatives/too low.
         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
@@ -2831,6 +3193,8 @@
 
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
         assertEquals(0, mQuotaController.getInQuotaBufferMs());
+        assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+        assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -2878,6 +3242,8 @@
         // Test larger than a day. Controller should cap at one day.
         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f);
+        setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
@@ -2905,6 +3271,8 @@
 
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
+        assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+        assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3669,8 +4037,8 @@
 
         // Wait for some extra time to allow for job processing.
         inOrder.verify(mJobSchedulerService,
-                timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
-                .onControllerStateChanged(any());
+                        timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
         synchronized (mQuotaController.mLock) {
             assertEquals(remainingTimeMs / 2,
                     mQuotaController.getRemainingExecutionTimeLocked(jobBg));
@@ -3681,8 +4049,8 @@
         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         advanceElapsedClock(remainingTimeMs / 2 + 1);
         inOrder.verify(mJobSchedulerService,
-                timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                        timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
         // Top job should still be allowed to run.
         assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3696,7 +4064,7 @@
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
         trackJobs(jobFg, jobTop);
         synchronized (mQuotaController.mLock) {
             mQuotaController.prepareForExecutionLocked(jobTop);
@@ -3715,7 +4083,7 @@
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
         // App is now in background and out of quota. Fg should now change to out of quota since it
         // wasn't started. Top should remain in quota since it started when the app was in TOP.
         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3753,7 +4121,7 @@
         // Wait for some extra time to allow for job processing.
         verify(mJobSchedulerService,
                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
         assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
                 jobStatus.getWhenStandbyDeferred());
@@ -3797,7 +4165,7 @@
         // Wait for some extra time to allow for job processing.
         verify(mJobSchedulerService,
                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
         // The job used up the remaining quota, but in that time, the same amount of time in the
         // old TimingSession also fell out of the quota window, so it should still have the same
@@ -3819,7 +4187,7 @@
         // Wait for some extra time to allow for job processing.
         verify(mJobSchedulerService,
                 timeout(12 * SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
         verify(handler, never()).sendMessageDelayed(any(), anyInt());
     }
@@ -4406,6 +4774,11 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null);
+        }
+
         final int standbyBucket = WORKING_INDEX;
         setStandbyBucket(standbyBucket);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
@@ -4482,6 +4855,11 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null);
+        }
+
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
@@ -4590,6 +4968,11 @@
         spyOn(mQuotaController);
         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
 
+        synchronized (mQuotaController.mLock) {
+            mQuotaController.maybeStartTrackingJobLocked(
+                    createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null);
+        }
+
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         setStandbyBucket(WORKING_INDEX);
         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
@@ -5437,8 +5820,8 @@
 
         // Wait for some extra time to allow for job processing.
         inOrder.verify(mJobSchedulerService,
-                timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
-                .onControllerStateChanged(any());
+                        timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
         synchronized (mQuotaController.mLock) {
             assertEquals(remainingTimeMs / 2,
                     mQuotaController.getRemainingEJExecutionTimeLocked(
@@ -5448,8 +5831,8 @@
         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         advanceElapsedClock(remainingTimeMs / 2 + 1);
         inOrder.verify(mJobSchedulerService,
-                timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                        timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
         // Top should still be "in quota" since it started before the app ran on top out of quota.
         assertFalse(jobBg.isExpeditedQuotaApproved());
         assertTrue(jobTop.isExpeditedQuotaApproved());
@@ -5473,7 +5856,7 @@
         setProcessState(ActivityManager.PROCESS_STATE_TOP);
         // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
         trackJobs(jobTop2, jobFg);
         synchronized (mQuotaController.mLock) {
             mQuotaController.prepareForExecutionLocked(jobTop2);
@@ -5494,7 +5877,7 @@
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
         // App is now in background and out of quota. Fg should now change to out of quota since it
         // wasn't started. Top should remain in quota since it started when the app was in TOP.
         assertTrue(jobTop2.isExpeditedQuotaApproved());
@@ -5633,7 +6016,7 @@
         // Wait for some extra time to allow for job processing.
         verify(mJobSchedulerService,
                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
-                .onControllerStateChanged(any());
+                .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
         assertTrue(jobStatus.isExpeditedQuotaApproved());
         // The job used up the remaining quota, but in that time, the same amount of time in the
         // old TimingSession also fell out of the quota window, so it should still have the same
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 555f4b8..1e0f30e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -521,6 +521,8 @@
         val parsedPackage = parseResult.hideAsParsed() as ParsedPackage
         whenever(mocks.packageParser.parsePackage(
                 or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
+        whenever(mocks.packageParser.parsePackage(
+                or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
         return parsedPackage
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index dbd5403..0820a3c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -33,6 +33,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.argThat
@@ -121,7 +122,8 @@
         whenever(rule.mocks().packageParser.parsePackage(
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
-                anyBoolean()))
+                anyBoolean(),
+                any()))
                 .thenThrow(PackageManagerException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 53468c8..ff1b6f6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,13 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.IInputConstants;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 
@@ -64,9 +66,13 @@
         final IBinder deviceToken = new Binder();
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
+        verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+        verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
         mInputController.unregisterInputDevice(deviceToken);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
                 eq(Display.INVALID_DISPLAY));
+        verify(mInputManagerInternalMock).setPointerAcceleration(
+                eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
     }
 
     @Test
@@ -75,10 +81,14 @@
         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
                 /* displayId= */ 1);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
-        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
+        verify(mInputManagerInternalMock).setPointerAcceleration(eq(1f));
+        final IBinder deviceToken2 = new Binder();
+        mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
                 /* displayId= */ 2);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
         mInputController.unregisterInputDevice(deviceToken);
         verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
+        verify(mInputManagerInternalMock, times(0)).setPointerAcceleration(
+                eq((float) IInputConstants.DEFAULT_POINTER_ACCELERATION));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index fe7d34a..72100e44 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -29,6 +29,7 @@
 
 import android.Manifest;
 import android.app.admin.DevicePolicyManager;
+import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -81,6 +82,8 @@
     private DevicePolicyManager mDevicePolicyManagerMock;
     @Mock
     private InputManagerInternal mInputManagerInternalMock;
+    @Mock
+    private IVirtualDeviceActivityListener mActivityListener;
 
     @Before
     public void setUp() {
@@ -102,7 +105,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {}, mPendingTrampolineCallback,
+                (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener,
                 new VirtualDeviceParams.Builder().build());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8a5b16e..c0ad69f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -42,7 +42,9 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
@@ -93,6 +95,7 @@
 import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.WifiSsidPolicy;
 import android.content.BroadcastReceiver;
@@ -4135,6 +4138,7 @@
         ProfileNetworkPreference preferenceDetails2 =
                 new ProfileNetworkPreference.Builder()
                         .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
                         .build();
         List<ProfileNetworkPreference> preferences2 = new ArrayList<>();
         preferences2.add(preferenceDetails);
@@ -4144,6 +4148,164 @@
     }
 
     @Test
+    public void testSetPreferentialNetworkServiceConfig_noProfileOwner() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setPreferentialNetworkServiceConfig(
+                        PreferentialNetworkServiceConfig.DEFAULT));
+    }
+
+    @Test
+    public void testIsPreferentialNetworkServiceEnabled_noProfileOwner() throws Exception {
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.isPreferentialNetworkServiceEnabled());
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_invalidConfig() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig.Builder preferentialNetworkServiceConfigBuilder =
+                new PreferentialNetworkServiceConfig.Builder();
+        assertExpectException(NullPointerException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setIncludedUids(null));
+        assertExpectException(NullPointerException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setExcludedUids(null));
+        assertExpectException(IllegalArgumentException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.setNetworkId(6));
+        int[] includedUids = new int[]{1, 2};
+        int[] excludedUids = new int[]{3, 4};
+        preferentialNetworkServiceConfigBuilder.setIncludedUids(includedUids);
+        preferentialNetworkServiceConfigBuilder.setExcludedUids(excludedUids);
+
+        assertExpectException(IllegalStateException.class, null,
+                () -> preferentialNetworkServiceConfigBuilder.build());
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_defaultPreference() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        dpm.setPreferentialNetworkServiceConfig(PreferentialNetworkServiceConfig.DEFAULT);
+        assertThat(dpm.isPreferentialNetworkServiceEnabled()).isFalse();
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isFalse();
+
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreference() throws Exception {
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .build();
+
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceIncludedUids()
+            throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .setFallbackToDefaultConnectionAllowed(false)
+                        .setIncludedUids(new int[]{1, 2})
+                        .build();
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        List<Integer> includedList = new ArrayList<>();
+        includedList.add(1);
+        includedList.add(2);
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .setIncludedUids(includedList)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
+    public void testSetPreferentialNetworkServiceConfig_enterprisePreferenceExcludedUids()
+            throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        PreferentialNetworkServiceConfig preferentialNetworkServiceConfigEnabled =
+                (new PreferentialNetworkServiceConfig.Builder())
+                        .setEnabled(true)
+                        .setNetworkId(NET_ENTERPRISE_ID_1)
+                        .setFallbackToDefaultConnectionAllowed(false)
+                        .setExcludedUids(new int[]{1, 2})
+                        .build();
+
+        dpm.setPreferentialNetworkServiceConfig(preferentialNetworkServiceConfigEnabled);
+        assertThat(dpm.getPreferentialNetworkServiceConfig()
+                .isEnabled()).isTrue();
+        List<Integer> excludedUids = new ArrayList<>();
+        excludedUids.add(1);
+        excludedUids.add(2);
+        ProfileNetworkPreference preferenceDetails =
+                new ProfileNetworkPreference.Builder()
+                        .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK)
+                        .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1)
+                        .setExcludedUids(excludedUids)
+                        .build();
+        List<ProfileNetworkPreference> preferences = new ArrayList<>();
+        preferences.clear();
+        preferences.add(preferenceDetails);
+        verify(getServices().connectivityManager, times(1))
+                .setProfileNetworkPreferences(UserHandle.of(managedProfileUserId), preferences,
+                        null, null);
+    }
+
+    @Test
     public void testSetSystemSettingFailWithNonWhitelistedSettings() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -7960,6 +8122,51 @@
                 () -> WifiSsidPolicy.createDenylistPolicy(ssids));
     }
 
+    @Test
+    public void testSendLostModeLocationUpdate_noPermission() {
+        assertThrows(SecurityException.class, () -> dpm.sendLostModeLocationUpdate(
+                getServices().executor, /* empty callback */ result -> {}));
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_notOrganizationOwnedDevice() {
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        assertThrows(IllegalStateException.class, () -> dpm.sendLostModeLocationUpdate(
+                getServices().executor, /* empty callback */ result -> {}));
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_asDeviceOwner() throws Exception {
+        final String TEST_PROVIDER = "network";
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        setDeviceOwner();
+        when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+        when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+        dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+        verify(getServices().locationManager, times(1)).getCurrentLocation(
+                eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+    }
+
+    @Test
+    public void testSendLostModeLocationUpdate_asProfileOwnerOfOrgOwnedDevice() throws Exception {
+        final String TEST_PROVIDER = "network";
+        final int MANAGED_PROFILE_ADMIN_UID =
+                UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        mContext.callerPermissions.add(permission.SEND_LOST_MODE_LOCATION_UPDATES);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
+        when(getServices().locationManager.getAllProviders()).thenReturn(List.of(TEST_PROVIDER));
+        when(getServices().locationManager.isProviderEnabled(TEST_PROVIDER)).thenReturn(true);
+
+        dpm.sendLostModeLocationUpdate(getServices().executor, /* empty callback */ result -> {});
+
+        verify(getServices().locationManager, times(1)).getCurrentLocation(
+                eq(TEST_PROVIDER), any(), eq(getServices().executor), any());
+    }
+
     private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
         final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
                 userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6eb2085..2cf67f8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -47,6 +47,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Context used throughout DPMS tests.
@@ -234,6 +235,8 @@
                 return mMockSystemServices.vpnManager;
             case Context.DEVICE_POLICY_SERVICE:
                 return mMockSystemServices.devicePolicyManager;
+            case Context.LOCATION_SERVICE:
+                return mMockSystemServices.locationManager;
         }
         throw new UnsupportedOperationException();
     }
@@ -493,6 +496,11 @@
     }
 
     @Override
+    public Executor getMainExecutor() {
+        return mMockSystemServices.executor;
+    }
+
+    @Override
     public int checkCallingPermission(String permission) {
         return checkPermission(permission);
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 597a165..21fb2da 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -47,6 +47,7 @@
 import android.content.pm.UserInfo;
 import android.database.Cursor;
 import android.hardware.usb.UsbManager;
+import android.location.LocationManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
 import android.net.IIpConnectivityMetrics;
@@ -81,6 +82,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -90,6 +92,7 @@
     public final File systemUserDataDir;
     public final EnvironmentForMock environment;
     public final SystemPropertiesForMock systemProperties;
+    public final Executor executor;
     public final UserManager userManager;
     public final UserManagerInternal userManagerInternal;
     public final UsageStatsManagerInternal usageStatsManagerInternal;
@@ -127,6 +130,7 @@
     public final UsbManager usbManager;
     public final VpnManager vpnManager;
     public final DevicePolicyManager devicePolicyManager;
+    public final LocationManager locationManager;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -138,6 +142,7 @@
 
         environment = mock(EnvironmentForMock.class);
         systemProperties = mock(SystemPropertiesForMock.class);
+        executor = mock(Executor.class);
         userManager = mock(UserManager.class);
         userManagerInternal = mock(UserManagerInternal.class);
         usageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
@@ -175,6 +180,7 @@
         usbManager = mock(UsbManager.class);
         vpnManager = mock(VpnManager.class);
         devicePolicyManager = mock(DevicePolicyManager.class);
+        locationManager = mock(LocationManager.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 03eba9b..d2cff0e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -551,13 +551,14 @@
         Assert.assertTrue(Arrays.equals(expected, actual));
     }
 
-    private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
+    private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
         private final DeviceStateProvider mProvider;
         private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
         private boolean mConfigureBlocked = false;
         private Runnable mPendingConfigureCompleteRunnable;
 
         TestDeviceStatePolicy(DeviceStateProvider provider) {
+            super(InstrumentationRegistry.getContext());
             mProvider = provider;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
new file mode 100644
index 0000000..0bd81b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStatePolicyProviderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicestate;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for the {@link DeviceStatePolicy.Provider}
+ * <p/>
+ * Build/Install/Run:
+ *  <code>atest DeviceStatePolicyProviderTest</code>
+ */
+@Presubmit
+public class DeviceStatePolicyProviderTest {
+
+    @Test
+    public void test_emptyPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider("")),
+                Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+    }
+
+    @Test
+    public void test_nullPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(null)),
+                Matchers.instanceOf(DeviceStatePolicy.DefaultProvider.class));
+    }
+
+    @Test
+    public void test_customPolicyProvider() {
+        Assert.assertThat(DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                TestProvider.class.getName())),
+                Matchers.instanceOf(TestProvider.class));
+    }
+
+    @Test
+    public void test_badPolicyProvider_notImplementingProviderInterface() {
+        assertThrows(IllegalStateException.class, () -> {
+            DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                    Object.class.getName()));
+        });
+    }
+
+    @Test
+    public void test_badPolicyProvider_doesntExist() {
+        assertThrows(IllegalStateException.class, () -> {
+            DeviceStatePolicy.Provider.fromResources(resourcesWithProvider(
+                    "com.android.devicestate.nonexistent.policy"));
+        });
+    }
+
+    private static Resources resourcesWithProvider(String provider) {
+        final Resources mockResources = mock(Resources.class);
+        when(mockResources.getString(
+                com.android.internal.R.string.config_deviceSpecificDeviceStatePolicyProvider))
+                .thenReturn(provider);
+        return mockResources;
+    }
+
+    // Stub implementation of DeviceStatePolicy.Provider for testing
+    static class TestProvider implements DeviceStatePolicy.Provider {
+        @Override
+        public DeviceStatePolicy instantiate(Context context) {
+            throw new RuntimeException("test stub");
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54945e4..4caa85c 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -391,4 +391,33 @@
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0));
         assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
     }
+
+    @Test
+    public void testHysteresisLevels() {
+        int[] ambientBrighteningThresholds = {100, 200};
+        int[] ambientDarkeningThresholds = {400, 500};
+        int[] ambientThresholdLevels = {500};
+        float ambientDarkeningMinChangeThreshold = 3.0f;
+        float ambientBrighteningMinChangeThreshold = 1.5f;
+        HysteresisLevels hysteresisLevels = new HysteresisLevels(ambientBrighteningThresholds,
+                ambientDarkeningThresholds, ambientThresholdLevels,
+                ambientDarkeningMinChangeThreshold, ambientBrighteningMinChangeThreshold);
+
+        // test low, activate minimum change thresholds.
+        assertEquals(1.5f, hysteresisLevels.getBrighteningThreshold(0.0f), EPSILON);
+        assertEquals(0f, hysteresisLevels.getDarkeningThreshold(0.0f), EPSILON);
+        assertEquals(1f, hysteresisLevels.getDarkeningThreshold(4.0f), EPSILON);
+
+        // test max
+        assertEquals(12000f, hysteresisLevels.getBrighteningThreshold(10000.0f), EPSILON);
+        assertEquals(5000f, hysteresisLevels.getDarkeningThreshold(10000.0f), EPSILON);
+
+        // test just below threshold
+        assertEquals(548.9f, hysteresisLevels.getBrighteningThreshold(499f), EPSILON);
+        assertEquals(299.4f, hysteresisLevels.getDarkeningThreshold(499f), EPSILON);
+
+        // test at (considered above) threshold
+        assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON);
+        assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 81c9871..e80721a 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -51,6 +51,7 @@
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.buildTemplateCarrierMetered;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -71,7 +72,6 @@
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
 import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons;
-import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -95,6 +95,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -495,8 +496,14 @@
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
-        // Simulate NetworkStatsService broadcast stats updated to signal its readiness.
-        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED));
+        // Catch UsageCallback during systemReady(). Simulate NetworkStatsService triggered
+        // stats updated callback to signal its readiness.
+        final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageObserver =
+                ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+        verify(mStatsManager, times(2))
+                .registerUsageCallback(any(), anyLong(), any(), usageObserver.capture());
+        usageObserver.getValue().onThresholdReached(
+                new NetworkTemplate.Builder(MATCH_MOBILE).build());
 
         NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, "");
         mDefaultWarningBytes = defaultPolicy.warningBytes;
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index d8ecf20..31bdec1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -65,7 +65,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.CompatibilityPermissionInfo;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedActivityImpl;
 import com.android.server.pm.pkg.component.ParsedApexSystemService;
@@ -673,9 +673,9 @@
         assertArrayEquals(a.getSplitFlags(), b.getSplitFlags());
 
         PackageInfo aInfo = PackageInfoUtils.generate(a, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(a));
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(a));
         PackageInfo bInfo = PackageInfoUtils.generate(b, new int[]{}, 0, 0, 0,
-                Collections.emptySet(), PackageUserState.DEFAULT, 0, mockPkgSetting(b));
+                Collections.emptySet(), PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(b));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
 
         assertEquals(ArrayUtils.size(a.getPermissions()), ArrayUtils.size(b.getPermissions()));
@@ -831,9 +831,9 @@
 
         // Validity check for ServiceInfo.
         ServiceInfo aInfo = PackageInfoUtils.generateServiceInfo(aPkg, a, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
         ServiceInfo bInfo = PackageInfoUtils.generateServiceInfo(bPkg, b, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
@@ -858,9 +858,9 @@
 
         // Validity check for ActivityInfo.
         ActivityInfo aInfo = PackageInfoUtils.generateActivityInfo(aPkg, a, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(aPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(aPkg));
         ActivityInfo bInfo = PackageInfoUtils.generateActivityInfo(bPkg, b, 0,
-                PackageUserState.DEFAULT, 0, mockPkgSetting(bPkg));
+                PackageUserStateInternal.DEFAULT, 0, mockPkgSetting(bPkg));
         assertApplicationInfoEqual(aInfo.applicationInfo, bInfo.applicationInfo);
         assertEquals(a.getName(), b.getName());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 7ff8eec..4a24bbd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -574,7 +574,7 @@
         assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
 
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertBasicApplicationInfo(scanResult, applicationInfo);
     }
 
@@ -612,7 +612,7 @@
     private static void assertAbiAndPathssDerived(ScanResult scanResult) {
         PackageSetting pkgSetting = scanResult.mPkgSetting;
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertThat(applicationInfo.primaryCpuAbi, is("derivedPrimary"));
         assertThat(applicationInfo.secondaryCpuAbi, is("derivedSecondary"));
 
@@ -626,7 +626,7 @@
     private static void assertPathsNotDerived(ScanResult scanResult) {
         PackageSetting pkgSetting = scanResult.mPkgSetting;
         final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
-                pkgSetting.getPkg(), 0, pkgSetting.readUserState(0), 0, pkgSetting);
+                pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
         assertThat(applicationInfo.nativeLibraryRootDir, is("getRootDir"));
         assertThat(pkgSetting.getLegacyNativeLibraryPath(), is("getRootDir"));
         assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(true));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7e5fe04..401cd7f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -309,7 +309,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_restrictedReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_restrictedReturnsError() throws Exception {
         final int currentUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -328,7 +328,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_evenWhenRestricted() throws Exception {
+    public void testRemoveUserWhenPossible_evenWhenRestricted() throws Exception {
         final int currentUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
@@ -351,7 +351,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_systemUserReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_systemUserReturnsError() throws Exception {
         assertThat(mUserManager.removeUserWhenPossible(UserHandle.SYSTEM,
                 /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
 
@@ -360,7 +360,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_invalidUserReturnsError() throws Exception {
+    public void testRemoveUserWhenPossible_invalidUserReturnsError() throws Exception {
         assertThat(hasUser(Integer.MAX_VALUE)).isFalse();
         assertThat(mUserManager.removeUserWhenPossible(UserHandle.of(Integer.MAX_VALUE),
                 /* overrideDevicePolicy= */ false)).isEqualTo(UserManager.REMOVE_RESULT_ERROR);
@@ -368,7 +368,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral() throws Exception {
+    public void testRemoveUserWhenPossible_currentUserSetEphemeral() throws Exception {
         final int startUser = ActivityManager.getCurrentUser();
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         // Switch to the user just created.
@@ -392,7 +392,7 @@
 
     @MediumTest
     @Test
-    public void testRemoveUserOrSetEphemeral_nonCurrentUserRemoved() throws Exception {
+    public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
         synchronized (mUserRemoveLock) {
             assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
index 739b3b1..5e9e16a 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/DeviceVibrationEffectAdapterTest.java
@@ -53,10 +53,10 @@
     private static final float[] TEST_AMPLITUDE_MAP = new float[]{
             /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
 
-    private static final VibratorInfo.FrequencyMapping EMPTY_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(Float.NaN, Float.NaN, Float.NaN, null);
-    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
+    private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(Float.NaN, Float.NaN, Float.NaN, null);
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_MIN_FREQUENCY,
                     TEST_FREQUENCY_RESOLUTION, TEST_AMPLITUDE_MAP);
 
     private DeviceVibrationEffectAdapter mAdapter;
@@ -79,8 +79,8 @@
                 new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
                 /* repeatIndex= */ -1);
 
-        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_MAPPING)));
-        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_MAPPING)));
+        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(EMPTY_FREQUENCY_PROFILE)));
+        assertEquals(effect, mAdapter.apply(effect, createVibratorInfo(TEST_FREQUENCY_PROFILE)));
     }
 
     @Test
@@ -97,7 +97,7 @@
                 /* repeatIndex= */ 3);
 
         VibrationEffect.Composed adaptedEffect = (VibrationEffect.Composed) mAdapter.apply(effect,
-                createVibratorInfo(EMPTY_FREQUENCY_MAPPING));
+                createVibratorInfo(EMPTY_FREQUENCY_PROFILE));
         assertTrue(adaptedEffect.getSegments().size() > effect.getSegments().size());
         assertTrue(adaptedEffect.getRepeatIndex() >= effect.getRepeatIndex());
 
@@ -128,7 +128,7 @@
                         /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
                 /* repeatIndex= */ 2);
 
-        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
+        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
                 IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         assertEquals(expected, mAdapter.apply(effect, info));
     }
@@ -159,7 +159,7 @@
                         /* duration= */ 20)),
                 /* repeatIndex= */ 2);
 
-        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_MAPPING,
+        VibratorInfo info = createVibratorInfo(EMPTY_FREQUENCY_PROFILE,
                 IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         assertEquals(expected, mAdapter.apply(effect, info));
     }
@@ -188,17 +188,17 @@
                         /* startFrequencyHz= */ 200, /* endFrequencyHz= */ 50, /* duration= */ 20)),
                 /* repeatIndex= */ 2);
 
-        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_MAPPING,
+        VibratorInfo info = createVibratorInfo(TEST_FREQUENCY_PROFILE,
                 IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         assertEquals(expected, mAdapter.apply(effect, info));
     }
 
-    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyMapping frequencyMapping,
+    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
             int... capabilities) {
         int cap = IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0);
         return new VibratorInfo.Builder(0)
                 .setCapabilities(cap)
-                .setFrequencyMapping(frequencyMapping)
+                .setFrequencyProfile(frequencyProfile)
                 .build();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2ad0e93..e88e988 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -170,7 +170,7 @@
             }
             infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
             infoBuilder.setQFactor(mQFactor);
-            infoBuilder.setFrequencyMapping(new VibratorInfo.FrequencyMapping(
+            infoBuilder.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
                     mResonantFrequency, mMinFrequency, mFrequencyResolution, mMaxAmplitudes));
             return mIsInfoLoadSuccessful;
         }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 22db917..a9f37f3 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -47,8 +47,8 @@
     private static final int TEST_STEP_DURATION = 5;
     private static final float[] TEST_AMPLITUDE_MAP = new float[]{
             /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
-    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(
                     /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
                     /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
 
@@ -124,7 +124,7 @@
     private static VibratorInfo createVibratorInfo(int... capabilities) {
         return new VibratorInfo.Builder(0)
                 .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
-                .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
                 .build();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
index 18ff953..54627c4 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -46,8 +46,8 @@
 public class StepToRampAdapterTest {
     private static final float[] TEST_AMPLITUDE_MAP = new float[]{
             /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
-    private static final VibratorInfo.FrequencyMapping TEST_FREQUENCY_MAPPING =
-            new VibratorInfo.FrequencyMapping(
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(
                     /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
                     /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
 
@@ -99,7 +99,7 @@
         VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
                 .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
                 .setPwlePrimitiveDurationMax(10)
-                .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
                 .build();
 
         // Update repeat index to skip the ramp splits.
@@ -191,7 +191,7 @@
     private static VibratorInfo createVibratorInfo(int... capabilities) {
         return new VibratorInfo.Builder(0)
                 .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
-                .setFrequencyMapping(TEST_FREQUENCY_MAPPING)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
                 .build();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
index cb4982b..f2c1874 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -310,13 +310,13 @@
     }
 
     private void mockVibratorCapabilities(int capabilities) {
-        VibratorInfo.FrequencyMapping frequencyMapping = new VibratorInfo.FrequencyMapping(
+        VibratorInfo.FrequencyProfile frequencyProfile = new VibratorInfo.FrequencyProfile(
                 Float.NaN, Float.NaN, Float.NaN, null);
         when(mNativeWrapperMock.getInfo(any(VibratorInfo.Builder.class)))
                 .then(invocation -> {
                     ((VibratorInfo.Builder) invocation.getArgument(0))
                             .setCapabilities(capabilities)
-                            .setFrequencyMapping(frequencyMapping);
+                            .setFrequencyProfile(frequencyProfile);
                     return true;
                 });
     }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ab86e29..52975ef 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -316,7 +316,7 @@
 
         assertNotNull(info);
         assertEquals(1, info.getId());
-        assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
     }
 
     @Test
@@ -341,7 +341,7 @@
                 info.isEffectSupported(VibrationEffect.EFFECT_TICK));
         assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK));
         assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK));
-        assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
         assertTrue(Float.isNaN(info.getQFactor()));
     }
 
@@ -360,7 +360,7 @@
         VibratorInfo info = createService().getVibratorInfo(1);
         assertNotNull(info);
         assertEquals(1, info.getId());
-        assertEquals(123.f, info.getResonantFrequency(), 0.01 /*tolerance*/);
+        assertEquals(123.f, info.getResonantFrequencyHz(), 0.01 /*tolerance*/);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a192bf8..9f92294 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -483,7 +483,7 @@
                 mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                 mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
                 mock(TelephonyManager.class),
-                mAmi, mToastRateLimiter, mPermissionHelper);
+                mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class));
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index f6400b6..da5496d 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -371,7 +371,8 @@
                 mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
                 mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                 mAppOpsManager, mock(IAppOpsService.class), mUm, mHistoryManager, mStatsManager,
-                mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper);
+                mock(TelephonyManager.class), mAmi, mToastRateLimiter, mPermissionHelper,
+                mock(UsageStatsManagerInternal.class));
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 59d9a35..1d25b54 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -166,7 +166,8 @@
                     mUm, mock(NotificationHistoryManager.class),
                     mock(StatsManager.class), mock(TelephonyManager.class),
                     mock(ActivityManagerInternal.class),
-                    mock(MultiRateLimiter.class), mock(PermissionHelper.class));
+                    mock(MultiRateLimiter.class), mock(PermissionHelper.class),
+                    mock(UsageStatsManagerInternal.class));
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                 throw e;
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 774e5b9..30ad1f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -40,6 +40,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+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;
@@ -83,6 +84,7 @@
 import static com.android.server.wm.ActivityRecord.State.STARTED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
@@ -3305,6 +3307,29 @@
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
     }
 
+    @Test // b/162542125
+    public void testInputDispatchTimeout() throws RemoteException {
+        final ActivityRecord activity = createActivityWithTask();
+        final WindowProcessController wpc = activity.app;
+        spyOn(wpc);
+        doReturn(true).when(wpc).isInstrumenting();
+        final ActivityRecord instrumentingActivity = createActivityOnDisplay(
+                true /* defaultDisplay */, wpc);
+        assertEquals(INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS,
+                instrumentingActivity.mInputDispatchingTimeoutMillis);
+
+        doReturn(false).when(wpc).isInstrumenting();
+        final ActivityRecord nonInstrumentingActivity = createActivityOnDisplay(
+                true /* defaultDisplay */, wpc);
+        assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+                nonInstrumentingActivity.mInputDispatchingTimeoutMillis);
+
+        final ActivityRecord noProcActivity = createActivityOnDisplay(true /* defaultDisplay */,
+                null);
+        assertEquals(DEFAULT_DISPATCHING_TIMEOUT_MILLIS,
+                noProcActivity.mInputDispatchingTimeoutMillis);
+    }
+
     private ICompatCameraControlCallback getCompatCameraControlCallback() {
         return new ICompatCameraControlCallback.Stub() {
             @Override
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 8d58ec0..ea03250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1340,7 +1340,7 @@
         displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
         displayContent.setRotationAnimation(rotationAnim);
         // The fade rotation animation also starts to hide some non-app windows.
-        assertNotNull(displayContent.getFadeRotationAnimationController());
+        assertNotNull(displayContent.getAsyncRotationController());
         assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
 
         for (WindowState w : windows) {
@@ -1354,7 +1354,7 @@
         // the animation controller should be cleared.
         statusBar.setOrientationChanging(false);
         navBar.setOrientationChanging(false);
-        assertNull(displayContent.getFadeRotationAnimationController());
+        assertNull(displayContent.getAsyncRotationController());
     }
 
     @UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
@@ -1392,7 +1392,7 @@
                 ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
                 false /* forceUpdate */));
 
-        assertNotNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNotNull(mDisplayContent.getAsyncRotationController());
         assertTrue(mStatusBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
         assertTrue(mNavBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
         // Notification shade may have its own view animation in real case so do not fade out it.
@@ -1482,7 +1482,7 @@
         assertFalse(app.hasFixedRotationTransform());
         assertFalse(app2.hasFixedRotationTransform());
         assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -1598,6 +1598,30 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
+    /**
+     * Creates different types of displays, verifies that minimal task size doesn't change
+     * with density of display.
+     */
+    @Test
+    public void testCalculatesDisplaySpecificMinTaskSizes() {
+        DisplayContent defaultTestDisplay =
+                new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
+        final int defaultMinTaskSize = defaultTestDisplay.mMinSizeOfResizeableTaskDp;
+        DisplayContent firstDisplay = new TestDisplayContent.Builder(mAtm, 1000, 2000)
+                .setDensityDpi(300)
+                .updateDisplayMetrics()
+                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 10)
+                .build();
+        assertEquals(defaultMinTaskSize + 10, firstDisplay.mMinSizeOfResizeableTaskDp);
+
+        DisplayContent secondDisplay = new TestDisplayContent.Builder(mAtm, 200, 200)
+                .setDensityDpi(320)
+                .updateDisplayMetrics()
+                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 20)
+                .build();
+        assertEquals(defaultMinTaskSize + 20, secondDisplay.mMinSizeOfResizeableTaskDp);
+    }
+
     @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         unblockDisplayRotation(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 6342183..25cff61c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
@@ -546,6 +547,80 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void testReturnsSensorRotation_180degrees_allRotationsAllowed()
+            throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testReturnLastRotation_sensor180_allRotationsNotAllowed()
+            throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testAllowRotationsIsCached()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+
+        // Rotate once to read the resource
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+        // Change resource to disallow all rotations.
+        // Rotate again and 180 degrees rotation should still be returned even if "disallowed".
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testResetAllowRotations()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+        enableOrientationSensor();
+
+        // Rotate once to read the resource
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(true);
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        mTarget.rotationForOrientation(SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0);
+
+        // Change resource to disallow all rotations.
+        // Reset "allowAllRotations".
+        // Rotate again and 180 degrees rotation should not be allowed anymore.
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_allowAllRotations))
+                .thenReturn(false);
+        mTarget.resetAllowAllRotations();
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_SENSOR, Surface.ROTATION_0));
+    }
+
     // =================================
     // Tests for Policy based Rotation
     // =================================
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 365e749..093be82 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -20,10 +20,10 @@
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED;
 import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -260,6 +260,17 @@
     }
 
     @Test
+    public void testResetAllowAllRotations() {
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        spyOn(mPrimaryDisplay);
+        doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
+
+        mDisplayWindowSettings.applyRotationSettingsToDisplayLocked(mPrimaryDisplay);
+
+        verify(displayRotation).resetAllowAllRotations();
+    }
+
+    @Test
     public void testDefaultToFreeUserRotation() {
         mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
similarity index 78%
rename from services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
rename to services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 407f9cf..670eb75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,11 +26,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityThread;
 import android.content.Context;
@@ -43,9 +45,9 @@
 import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.window.WindowTokenClient;
 
-import com.android.server.inputmethod.InputMethodManagerService;
-import com.android.server.inputmethod.InputMethodMenuController;
+import com.android.server.inputmethod.InputMethodDialogWindowContext;
 
 import org.junit.After;
 import org.junit.Before;
@@ -57,13 +59,13 @@
 //  scenario there.
 /**
  * Build/Install/Run:
- *  atest WmTests:InputMethodMenuControllerTest
+ *  atest WmTests:InputMethodDialogWindowContextTest
  */
 @Presubmit
 @RunWith(WindowTestRunner.class)
-public class InputMethodMenuControllerTest extends WindowTestsBase {
+public class InputMethodDialogWindowContextTest extends WindowTestsBase {
 
-    private InputMethodMenuController mController;
+    private InputMethodDialogWindowContext mWindowContext;
     private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay;
 
     private IWindowManager mIWindowManager;
@@ -76,7 +78,7 @@
                 new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
         Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
 
-        mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
+        mWindowContext = new InputMethodDialogWindowContext();
         mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
                 .Builder(mAtm, 1000, 1000).build();
         mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
@@ -115,30 +117,46 @@
 
     @Test
     public void testGetSettingsContext() {
-        final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY);
+        final Context contextOnDefaultDisplay = mWindowContext.get(DEFAULT_DISPLAY);
 
         assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);
 
         // Obtain the context again and check if the window metrics match the IME container bounds
         // of the secondary display.
-        final Context contextOnSecondaryDisplay = mController.getSettingsContext(
-                mSecondaryDisplay.getDisplayId());
+        final Context contextOnSecondaryDisplay =
+                mWindowContext.get(mSecondaryDisplay.getDisplayId());
 
         assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
     }
 
     @Test
     public void testGetSettingsContextOnDualDisplayContent() {
-        final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId());
+        final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+        final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
+        assertNotNull(tokenClient);
+        spyOn(tokenClient);
 
         final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
+        spyOn(imeContainer);
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
 
         mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+
+        verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+                eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+        verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+                eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+                eq(mSecondaryDisplay.mDisplayId));
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
         assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
 
         mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+
+        verify(imeContainer, atLeastOnce()).onConfigurationChanged(
+                eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+        verify(tokenClient, atLeastOnce()).onConfigurationChanged(
+                eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+                eq(mSecondaryDisplay.mDisplayId));
         assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
         assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 632a59d..87f76fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -621,9 +621,9 @@
     @Test
     public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
         setupForShouldAttachNavBarDuringTransition();
-        FadeRotationAnimationController mockController =
-                mock(FadeRotationAnimationController.class);
-        doReturn(mockController).when(mDefaultDisplay).getFadeRotationAnimationController();
+        AsyncRotationController mockController =
+                mock(AsyncRotationController.class);
+        doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
         final ActivityRecord homeActivity = createHomeActivity();
         initializeRecentsAnimationController(mController, homeActivity);
         assertFalse(mController.isNavigationBarAttachedToApp());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 575e082..a4851ad5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -665,9 +665,9 @@
 
     @Test
     public void testNonAppTarget_notSendNavBar_controlledByFadeRotation() throws Exception {
-        final FadeRotationAnimationController mockController =
-                mock(FadeRotationAnimationController.class);
-        doReturn(mockController).when(mDisplayContent).getFadeRotationAnimationController();
+        final AsyncRotationController mockController =
+                mock(AsyncRotationController.class);
+        doReturn(mockController).when(mDisplayContent).getAsyncRotationController();
         final int transit = TRANSIT_OLD_TASK_OPEN;
         setupForNonAppTargetNavBar(transit, true);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 8b0716c..1e64e46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -29,18 +29,26 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
+import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 class TestDisplayContent extends DisplayContent {
 
     public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -85,6 +93,11 @@
         private boolean mSystemDecorations = false;
         private int mStatusBarHeight = 0;
         private SettingsEntry mOverrideSettings;
+        private DisplayMetrics mDisplayMetrics;
+        @Mock
+        Context mMockContext;
+        @Mock
+        Resources mResources;
 
         Builder(ActivityTaskManagerService service, int width, int height) {
             mService = service;
@@ -97,6 +110,8 @@
             // Set unique ID so physical display overrides are not inheritted from
             // DisplayWindowSettings.
             mInfo.uniqueId = generateUniqueId();
+            mDisplayMetrics = new DisplayMetrics();
+            updateDisplayMetrics();
         }
         Builder(ActivityTaskManagerService service, DisplayInfo info) {
             mService = service;
@@ -153,6 +168,20 @@
             mInfo.logicalDensityDpi = dpi;
             return this;
         }
+        Builder updateDisplayMetrics() {
+            mInfo.getAppMetrics(mDisplayMetrics);
+            return this;
+        }
+        Builder setDefaultMinTaskSizeDp(int valueDp) {
+            MockitoAnnotations.initMocks(this);
+            doReturn(mMockContext).when(mService.mContext).createConfigurationContext(any());
+            doReturn(mResources).when(mMockContext).getResources();
+            doReturn(valueDp * mDisplayMetrics.density)
+                    .when(mResources)
+                    .getDimension(
+                        com.android.internal.R.dimen.default_minimal_size_resizable_task);
+            return this;
+        }
         TestDisplayContent createInternal(Display display) {
             return new TestDisplayContent(mService.mRootWindowContainer, display);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 141588a..fd523f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -50,6 +50,7 @@
 import android.util.ArraySet;
 import android.view.SurfaceControl;
 import android.view.TransactionCommittedListener;
+import android.window.IDisplayAreaOrganizer;
 import android.window.ITaskOrganizer;
 import android.window.ITransitionPlayer;
 import android.window.TransitionInfo;
@@ -60,6 +61,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -75,7 +78,9 @@
     private Transition createTestTransition(int transitType) {
         TransitionController controller = mock(TransitionController.class);
         final BLASTSyncEngine sync = createTestBLASTSyncEngine();
-        return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync);
+        final Transition t = new Transition(transitType, 0 /* flags */, controller, sync);
+        t.startCollecting(0 /* timeoutMs */);
+        return t;
     }
 
     @Test
@@ -104,7 +109,7 @@
         // Check basic both tasks participating
         participants.add(oldTask);
         participants.add(newTask);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
         assertEquals(transit, info.getType());
@@ -169,7 +174,7 @@
         participants.add(oldTask);
         participants.add(opening);
         participants.add(opening2);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
         assertEquals(transit, info.getType());
@@ -215,15 +220,14 @@
         // Check promotion to DisplayArea
         participants.add(showing);
         participants.add(showing2);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(1, info.getChanges().size());
         assertEquals(transit, info.getType());
         assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
 
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         // Check that organized tasks get reported even if not top
-        showTask.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(showTask);
         targets = Transition.calculateTargets(participants, changes);
         info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         assertEquals(2, info.getChanges().size());
@@ -253,7 +257,7 @@
         opening.mVisibleRequested = true;
         closing.mVisibleRequested = false;
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -290,7 +294,7 @@
             tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
         }
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -339,7 +343,7 @@
             tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
         }
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -361,13 +365,7 @@
                 mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
         // Make DA organized so we can check that they don't get included.
         WindowContainer parent = wallpaperWindowToken.getParent();
-        while (parent != null && parent != mDisplayContent) {
-            if (parent.asDisplayArea() != null) {
-                parent.asDisplayArea().setOrganizer(
-                        mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */);
-            }
-            parent = parent.getParent();
-        }
+        makeDisplayAreaOrganized(parent, mDisplayContent);
         final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
                 "wallpaperWindow");
         wallpaperWindowToken.setVisibleRequested(false);
@@ -379,7 +377,7 @@
         mDisplayContent.getWindowConfiguration().setRotation(
                 (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
 
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(
+        ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
         TransitionInfo info = Transition.calculateTransitionInfo(
                 0, 0, targets, transition.mChanges);
@@ -392,11 +390,37 @@
     }
 
     @Test
+    public void testOpenActivityInTheSameTaskWithDisplayChange() {
+        final ActivityRecord closing = createActivityRecord(mDisplayContent);
+        closing.mVisibleRequested = true;
+        final Task task = closing.getTask();
+        makeTaskOrganized(task);
+        final ActivityRecord opening = createActivityRecord(task);
+        opening.mVisibleRequested = false;
+        makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
+        final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        for (WindowContainer<?> wc : wcs) {
+            transition.collect(wc);
+        }
+        closing.mVisibleRequested = false;
+        opening.mVisibleRequested = true;
+        final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
+        for (WindowContainer<?> wc : wcs) {
+            wc.getWindowConfiguration().setRotation(newRotation);
+        }
+
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                transition.mParticipants, transition.mChanges);
+        // Especially the activities must be in the targets.
+        assertTrue(targets.containsAll(Arrays.asList(wcs)));
+    }
+
+    @Test
     public void testIndependent() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
         ArraySet<WindowContainer> participants = transition.mParticipants;
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
 
         final Task openTask = createTask(mDisplayContent);
         final Task openInOpenTask = createTaskInRootTask(openTask, 0);
@@ -408,10 +432,8 @@
         final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask);
         final ActivityRecord openInChange = createActivityRecord(openInChangeTask);
         // set organizer for everything so that they all get added to transition info
-        for (Task t : new Task[]{
-                openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) {
-            t.mTaskOrganizer = mockOrg;
-        }
+        makeTaskOrganized(openTask, openInOpenTask, changeTask, changeInChangeTask,
+                openInChangeTask);
 
         // Start states.
         changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
@@ -438,7 +460,8 @@
         participants.add(openInChange);
         // Explicitly add changeTask (to test independence with parents)
         participants.add(changeTask);
-        ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes);
+        final ArrayList<WindowContainer> targets =
+                Transition.calculateTargets(participants, changes);
         TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
         // Root changes should always be considered independent
         assertTrue(isIndependent(
@@ -466,12 +489,13 @@
         final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
         final CountDownLatch latch = new CountDownLatch(1);
         // When the timeout is reached, it will finish the sync-group and notify transaction ready.
-        new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) {
+        final Transition t = new Transition(TRANSIT_OPEN, 0 /* flags */, controller, sync) {
             @Override
             public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
                 latch.countDown();
             }
         };
+        t.startCollecting(10 /* timeoutMs */);
         assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
     }
 
@@ -491,9 +515,9 @@
         mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
                 null /* displayChange */);
-        final FadeRotationAnimationController fadeController =
-                mDisplayContent.getFadeRotationAnimationController();
-        assertNotNull(fadeController);
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        assertNotNull(asyncRotationController);
         for (WindowState w : windows) {
             w.setOrientationChanging(true);
         }
@@ -506,7 +530,7 @@
         // Status bar finishes drawing before the start transaction. Its fade-in animation will be
         // executed until the transaction is committed, so it is still in target tokens.
         statusBar.setOrientationChanging(false);
-        assertTrue(fadeController.isTargetToken(statusBar.mToken));
+        assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
 
         final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
         final ArgumentCaptor<TransactionCommittedListener> listenerCaptor =
@@ -516,13 +540,13 @@
         verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
         // The transaction is committed, so fade-in animation for status bar is consumed.
         listenerCaptor.getValue().onTransactionCommitted();
-        assertFalse(fadeController.isTargetToken(statusBar.mToken));
+        assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
 
         // Status bar finishes drawing after the start transaction, so its fade-in animation can
         // execute directly.
         navBar.setOrientationChanging(false);
-        assertFalse(fadeController.isTargetToken(navBar.mToken));
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -540,10 +564,10 @@
         mDisplayContent.setLastHasContent();
         mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
         transition.setKnownConfigChanges(mDisplayContent, anyChanges);
-        final FadeRotationAnimationController fadeController =
-                mDisplayContent.getFadeRotationAnimationController();
-        assertNotNull(fadeController);
-        assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar));
+        final AsyncRotationController asyncRotationController =
+                mDisplayContent.getAsyncRotationController();
+        assertNotNull(asyncRotationController);
+        assertTrue(asyncRotationController.shouldFreezeInsetsPosition(statusBar));
 
         statusBar.setOrientationChanging(true);
         player.startTransition();
@@ -569,7 +593,7 @@
         // The controller should capture the draw transaction and merge it when preparing to run
         // fade-in animation.
         verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction));
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -578,17 +602,15 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
         final ITransitionPlayer player = new ITransitionPlayer.Default();
         controller.registerTransitionPlayer(player, null /* appThread */);
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
-        task1.mTaskOrganizer = mockOrg;
         final ActivityRecord activity1 = createActivityRecord(task1);
         activity1.mVisibleRequested = false;
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
-        task2.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task1);
         activity2.mVisibleRequested = true;
         activity2.setVisible(true);
@@ -644,17 +666,15 @@
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
         final ITransitionPlayer player = new ITransitionPlayer.Default();
         controller.registerTransitionPlayer(player, null /* appThread */);
-        ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
         final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
 
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
-        task1.mTaskOrganizer = mockOrg;
         final ActivityRecord activity1 = createActivityRecord(task1);
         activity1.mVisibleRequested = false;
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
-        task2.mTaskOrganizer = mockOrg;
+        makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity2.mVisibleRequested = true;
         activity2.setVisible(true);
@@ -703,6 +723,24 @@
         verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
     }
 
+    private static void makeTaskOrganized(Task... tasks) {
+        final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+        for (Task t : tasks) {
+            t.mTaskOrganizer = organizer;
+        }
+    }
+
+    private static void makeDisplayAreaOrganized(WindowContainer<?> from,
+            WindowContainer<?> end) {
+        final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+        while (from != null && from != end) {
+            if (from.asDisplayArea() != null) {
+                from.asDisplayArea().mOrganizer = organizer;
+            }
+            from = from.getParent();
+        }
+    }
+
     /** Fill the change map with all the parents of top. Change maps are usually fully populated */
     private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
             WindowContainer top) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index caaf4e4..1f68608 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -116,6 +117,7 @@
 
         WindowManager.LayoutParams attrs = wallpaperWindow.getAttrs();
         Rect bounds = dc.getBounds();
+        int displayWidth = dc.getBounds().width();
         int displayHeight = dc.getBounds().height();
 
         // Use a wallpaper with a different ratio than the display
@@ -123,20 +125,18 @@
         int wallpaperHeight = (int) (bounds.height() * 1.10);
 
         // Simulate what would be done on the client's side
-        attrs.width = wallpaperWidth;
-        attrs.height = wallpaperHeight;
-        attrs.flags |= FLAG_LAYOUT_NO_LIMITS;
+        final float layoutScale = Math.max(
+                displayWidth / (float) wallpaperWidth, displayHeight / (float) wallpaperHeight);
+        attrs.width = (int) (wallpaperWidth * layoutScale + .5f);
+        attrs.height = (int) (wallpaperHeight * layoutScale + .5f);
+        attrs.flags |= FLAG_LAYOUT_NO_LIMITS | FLAG_SCALED;
         attrs.gravity = Gravity.TOP | Gravity.LEFT;
         wallpaperWindow.getWindowFrames().mParentFrame.set(dc.getBounds());
 
-        // Calling layoutWindowLw a first time, so adjustWindowParams gets the correct data
-        dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
-
-        wallpaperWindowToken.adjustWindowParams(wallpaperWindow, attrs);
         dc.getDisplayPolicy().layoutWindowLw(wallpaperWindow, null, dc.mDisplayFrames);
 
         assertEquals(Configuration.ORIENTATION_PORTRAIT, dc.getConfiguration().orientation);
-        int expectedWidth = (int) (wallpaperWidth * (displayHeight / (double) wallpaperHeight));
+        int expectedWidth = (int) (wallpaperWidth * layoutScale + .5f);
 
         // Check that the wallpaper is correctly scaled
         assertEquals(expectedWidth, wallpaperWindow.getFrame().width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 62c1067..4095728 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -883,7 +883,8 @@
 
     /** Sets the default minimum task size to 1 so that tests can use small task sizes */
     public void removeGlobalMinSizeRestriction() {
-        mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+        mAtm.mRootWindowContainer.forAllDisplays(
+                displayContent -> displayContent.mMinSizeOfResizeableTaskDp = 1);
     }
 
     /** Mocks the behavior of taking a snapshot. */
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
deleted file mode 100644
index 926153d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/utils/DisplayRotationUtilTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
-import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
-import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static com.android.server.wm.utils.DisplayRotationUtil.getBoundIndexFromRotation;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Tests for {@link DisplayRotationUtil}
- *
- * Build/Install/Run:
- *  atest WmTests:DisplayRotationUtilTest
- */
-@SmallTest
-@Presubmit
-public class DisplayRotationUtilTest {
-    private static final Rect ZERO_RECT = new Rect();
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot0() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_0),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_0),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_0),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_0),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot90() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_90),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_90),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_90),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_90),
-                equalTo(BOUNDS_POSITION_RIGHT));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot180() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_180),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_180),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_180),
-                equalTo(BOUNDS_POSITION_LEFT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_180),
-                equalTo(BOUNDS_POSITION_TOP));
-    }
-
-    @Test
-    public void testGetBoundIndexFromRotation_rot270() {
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_LEFT, ROTATION_270),
-                equalTo(BOUNDS_POSITION_TOP));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_TOP, ROTATION_270),
-                equalTo(BOUNDS_POSITION_RIGHT));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_RIGHT, ROTATION_270),
-                equalTo(BOUNDS_POSITION_BOTTOM));
-        assertThat(getBoundIndexFromRotation(BOUNDS_POSITION_BOTTOM, ROTATION_270),
-                equalTo(BOUNDS_POSITION_LEFT));
-
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot0() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_0, 200, 300),
-                equalTo(bounds));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot90() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_90, 200, 300),
-                equalTo(new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot180() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_180, 200, 300),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_top_rot270() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_270, 200, 300),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot0() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_0, 300, 200),
-                equalTo(bounds));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot90() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_90, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(50, 290, 150, 300)}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot180() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_180, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(290, 50, 300, 150), ZERO_RECT}));
-    }
-
-    @Test
-    public void testGetRotatedBounds_left_rot270() {
-        DisplayRotationUtil util = new DisplayRotationUtil();
-        Rect[] bounds = new Rect[] {new Rect(0, 50, 10, 150), ZERO_RECT, ZERO_RECT, ZERO_RECT};
-        assertThat(util.getRotatedBounds(bounds, ROTATION_270, 300, 200),
-                equalTo(new Rect[] {ZERO_RECT, new Rect(50, 0, 150, 10), ZERO_RECT, ZERO_RECT}));
-    }
-}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 997c883..559eb38 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -33,6 +33,7 @@
 
 import android.Manifest;
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -2947,6 +2948,27 @@
                 @NonNull EstimatedLaunchTimeChangedListener listener) {
             UsageStatsService.this.unregisterLaunchTimeChangedListener(listener);
         }
+
+        @Override
+        public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage,
+                @NonNull UserHandle targetUser, long idForResponseEvent,
+                @ElapsedRealtimeLong long timestampMs) {
+        }
+
+        @Override
+        public void reportNotificationPosted(@NonNull String packageName,
+                @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+        }
+
+        @Override
+        public void reportNotificationUpdated(@NonNull String packageName,
+                @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+        }
+
+        @Override
+        public void reportNotificationRemoved(@NonNull String packageName,
+                @NonNull UserHandle user, @ElapsedRealtimeLong long timestampMs) {
+        }
     }
 
     private class MyPackageMonitor extends PackageMonitor {
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 98173ad..65b79bf 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -384,6 +384,46 @@
     }
 
     /**
+     * Enables USB data when disabled due to {@link UsbPortStatus#USB_DATA_STATUS_DISABLED_DOCK}
+     */
+    public void enableUsbDataWhileDocked(@NonNull String portId, long transactionId,
+            IUsbOperationInternal callback, IndentingPrintWriter pw) {
+        Objects.requireNonNull(portId);
+        final PortInfo portInfo = mPorts.get(portId);
+        if (portInfo == null) {
+            logAndPrint(Log.ERROR, pw, "enableUsbDataWhileDocked: No such port: " + portId
+                    + " opId:" + transactionId);
+            try {
+                if (callback != null) {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(pw,
+                        "enableUsbDataWhileDocked: Failed to call OperationComplete. opId:"
+                        + transactionId, e);
+            }
+            return;
+        }
+
+        try {
+            try {
+                mUsbPortHal.enableUsbDataWhileDocked(portId, transactionId, callback);
+            } catch (Exception e) {
+                logAndPrintException(pw,
+                    "enableUsbDataWhileDocked: Failed to limit power transfer. opId:"
+                    + transactionId , e);
+                if (callback != null) {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw,
+                    "enableUsbDataWhileDocked:Failed to call onOperationComplete. opId:"
+                    + transactionId, e);
+        }
+    }
+
+    /**
      * Enable/disable the USB data signaling
      *
      * @param enable enable or disable USB data signaling
@@ -759,8 +799,9 @@
                         portInfo.contaminantProtectionStatus,
                         portInfo.supportsEnableContaminantPresenceDetection,
                         portInfo.contaminantDetectionStatus,
-                        portInfo.usbDataEnabled,
-                        portInfo.powerTransferLimited, pw);
+                        portInfo.usbDataStatus,
+                        portInfo.powerTransferLimited,
+                        portInfo.powerBrickStatus, pw);
             }
         } else {
             for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -773,8 +814,9 @@
                         currentPortInfo.contaminantProtectionStatus,
                         currentPortInfo.supportsEnableContaminantPresenceDetection,
                         currentPortInfo.contaminantDetectionStatus,
-                        currentPortInfo.usbDataEnabled,
-                        currentPortInfo.powerTransferLimited, pw);
+                        currentPortInfo.usbDataStatus,
+                        currentPortInfo.powerTransferLimited,
+                        currentPortInfo.powerBrickStatus, pw);
             }
         }
 
@@ -810,8 +852,9 @@
             int contaminantProtectionStatus,
             boolean supportsEnableContaminantPresenceDetection,
             int contaminantDetectionStatus,
-            boolean usbDataEnabled,
+            int[] usbDataStatus,
             boolean powerTransferLimited,
+            int powerBrickStatus,
             IndentingPrintWriter pw) {
         // Only allow mode switch capability for dual role ports.
         // Validate that the current mode matches the supported modes we expect.
@@ -870,8 +913,8 @@
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
-                    contaminantDetectionStatus, usbDataEnabled,
-                    powerTransferLimited);
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus);
             mPorts.put(portId, portInfo);
         } else {
             // Validate that ports aren't changing definition out from under us.
@@ -908,8 +951,8 @@
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
-                    contaminantDetectionStatus, usbDataEnabled,
-                    powerTransferLimited)) {
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1135,7 +1178,9 @@
                     != supportedRoleCombinations) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
-                        UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED, true, false);
+                        UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
+                        new int[]{UsbPortStatus.USB_DATA_STATUS_UNKNOWN}, false,
+                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
                 dispositionChanged = true;
             }
 
@@ -1150,12 +1195,31 @@
             return dispositionChanged;
         }
 
+        private boolean dataStatusEquals(int[] dataStatusL, int[] dataStatusR) {
+            if (dataStatusL == null && dataStatusR == null) {
+                return true;
+            }
+            if ((dataStatusL == null && dataStatusR != null)
+                || (dataStatusL != null && dataStatusR == null)) {
+                return false;
+            }
+            if (dataStatusL.length != dataStatusR.length) {
+                return false;
+            }
+            for (int i = 0; i < dataStatusL.length; i++) {
+                if (dataStatusL[i] != dataStatusR[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
         public boolean setStatus(int currentMode, boolean canChangeMode,
                 int currentPowerRole, boolean canChangePowerRole,
                 int currentDataRole, boolean canChangeDataRole,
                 int supportedRoleCombinations, int contaminantProtectionStatus,
-                int contaminantDetectionStatus, boolean usbDataEnabled,
-                boolean powerTransferLimited) {
+                int contaminantDetectionStatus, int[] usbDataStatus,
+                boolean powerTransferLimited, int powerBrickStatus) {
             boolean dispositionChanged = false;
 
             mCanChangeMode = canChangeMode;
@@ -1171,14 +1235,15 @@
                     != contaminantProtectionStatus
                     || mUsbPortStatus.getContaminantDetectionStatus()
                     != contaminantDetectionStatus
-                    || mUsbPortStatus.getUsbDataStatus()
-                    != usbDataEnabled
+                    || !dataStatusEquals(mUsbPortStatus.getUsbDataStatus(), usbDataStatus)
                     || mUsbPortStatus.isPowerTransferLimited()
-                    != powerTransferLimited) {
+                    != powerTransferLimited
+                    || mUsbPortStatus.getPowerBrickStatus()
+                    != powerBrickStatus) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, contaminantProtectionStatus,
-                        contaminantDetectionStatus, usbDataEnabled,
-                        powerTransferLimited);
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickStatus);
                 dispositionChanged = true;
             }
 
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 51643e7..88ffc7d61 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -814,6 +814,32 @@
     }
 
     @Override
+    public void enableUsbDataWhileDocked(String portId, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
+                + operationId);
+        Objects.requireNonNull(callback,
+                "enableUsbDataWhileDocked: callback must not be null. opId:"
+                + operationId);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        final long ident = Binder.clearCallingIdentity();
+        boolean wait;
+        try {
+            if (mPortManager != null) {
+                mPortManager.enableUsbDataWhileDocked(portId, operationId, callback, null);
+            } else {
+                try {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
         synchronized (mLock) {
diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
index 8dfc859..dd25620 100644
--- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -37,8 +37,9 @@
     public int contaminantProtectionStatus;
     public boolean supportsEnableContaminantPresenceDetection;
     public int contaminantDetectionStatus;
-    public boolean usbDataEnabled;
+    public int[] usbDataStatus;
     public boolean powerTransferLimited;
+    public int powerBrickStatus;
 
     public RawPortInfo(String portId, int supportedModes) {
         this.portId = portId;
@@ -48,8 +49,9 @@
         this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
         this.supportsEnableContaminantPresenceDetection = false;
         this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
-        this.usbDataEnabled = true;
+        this.usbDataStatus[0] = UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
         this.powerTransferLimited = false;
+        this.powerBrickStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
     }
 
     public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
@@ -60,8 +62,9 @@
             int contaminantProtectionStatus,
             boolean supportsEnableContaminantPresenceDetection,
             int contaminantDetectionStatus,
-            boolean usbDataEnabled,
-            boolean powerTransferLimited) {
+            int[] usbDataStatus,
+            boolean powerTransferLimited,
+            int powerBrickStatus) {
         this.portId = portId;
         this.supportedModes = supportedModes;
         this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
@@ -77,8 +80,9 @@
         this.supportsEnableContaminantPresenceDetection =
                 supportsEnableContaminantPresenceDetection;
         this.contaminantDetectionStatus = contaminantDetectionStatus;
-        this.usbDataEnabled = usbDataEnabled;
+        this.usbDataStatus = usbDataStatus;
         this.powerTransferLimited = powerTransferLimited;
+        this.powerBrickStatus = powerBrickStatus;
     }
 
     @Override
@@ -101,8 +105,10 @@
         dest.writeInt(contaminantProtectionStatus);
         dest.writeBoolean(supportsEnableContaminantPresenceDetection);
         dest.writeInt(contaminantDetectionStatus);
-        dest.writeBoolean(usbDataEnabled);
+        dest.writeInt(usbDataStatus.length);
+        dest.writeIntArray(usbDataStatus);
         dest.writeBoolean(powerTransferLimited);
+        dest.writeInt(powerBrickStatus);
     }
 
     public static final Parcelable.Creator<RawPortInfo> CREATOR =
@@ -122,8 +128,10 @@
             int contaminantProtectionStatus = in.readInt();
             boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
             int contaminantDetectionStatus = in.readInt();
-            boolean usbDataEnabled = in.readBoolean();
+            int[] usbDataStatus = new int[in.readInt()];
+            in.readIntArray(usbDataStatus);
             boolean powerTransferLimited = in.readBoolean();
+            int powerBrickStatus = in.readInt();
             return new RawPortInfo(id, supportedModes,
                     supportedContaminantProtectionModes, currentMode, canChangeMode,
                     currentPowerRole, canChangePowerRole,
@@ -131,8 +139,8 @@
                     supportsEnableContaminantPresenceProtection,
                     contaminantProtectionStatus,
                     supportsEnableContaminantPresenceDetection,
-                    contaminantDetectionStatus, usbDataEnabled,
-                    powerTransferLimited);
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus);
         }
 
         @Override
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index 2460242..5582600 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -322,6 +322,48 @@
         }
     }
 
+    @Override
+    public void enableUsbDataWhileDocked(String portName, long operationID,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portName);
+        long key = operationID;
+        synchronized (mLock) {
+            try {
+                if (mProxy == null) {
+                    logAndPrint(Log.ERROR, mPw,
+                            "enableUsbDataWhileDocked: Proxy is null. Retry !opID:"
+                            + operationID);
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    return;
+                }
+                while (sCallbacks.get(key) != null) {
+                    key = ThreadLocalRandom.current().nextInt();
+                }
+                if (key != operationID) {
+                    logAndPrint(Log.INFO, mPw,
+                            "enableUsbDataWhileDocked: operationID exists ! opID:"
+                            + operationID + " key:" + key);
+                }
+                try {
+                    sCallbacks.put(key, callback);
+                    mProxy.enableUsbDataWhileDocked(portName, key);
+                } catch (RemoteException e) {
+                    logAndPrintException(mPw,
+                            "enableUsbDataWhileDocked: error while invoking hal"
+                            + "portID=" + portName + " opID:" + operationID, e);
+                    if (callback != null) {
+                        callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    }
+                    sCallbacks.remove(key);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "enableUsbDataWhileDocked: Failed to call onOperationComplete portID="
+                        + portName + " opID:" + operationID, e);
+            }
+        }
+    }
+
     private static class HALCallback extends IUsbCallback.Stub {
         public IndentingPrintWriter mPw;
         public UsbPortManager mPortManager;
@@ -403,6 +445,14 @@
             return supportedContaminantProtectionModes;
         }
 
+        private int[] toIntArray(byte[] input) {
+            int[] output = new int[input.length];
+            for (int i = 0; i < input.length; i++) {
+                output[i] = input[i];
+            }
+            return output;
+        }
+
         @Override
         public void notifyPortStatusChange(
                android.hardware.usb.PortStatus[] currentPortStatus, int retval) {
@@ -434,8 +484,9 @@
                         toContaminantProtectionStatus(current.contaminantProtectionStatus),
                         current.supportsEnableContaminantPresenceDetection,
                         current.contaminantDetectionStatus,
-                        current.usbDataEnabled,
-                        current.powerTransferLimited);
+                        toIntArray(current.usbDataStatus),
+                        current.powerTransferLimited,
+                        current.powerBrickStatus);
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
                         + current.portName);
@@ -529,5 +580,30 @@
                         e);
             }
         }
+
+        @Override
+        public void notifyEnableUsbDataWhileDockedStatus(String portName, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:"
+                        + operationID + " successful");
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName
+                        + "notifyEnableUsbDataWhileDockedStatus: opID:"
+                        + operationID + " failed. err:" + retval);
+            }
+            try {
+                IUsbOperationInternal callback = sCallbacks.get(operationID);
+                if (callback != null) {
+                    sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS
+                            ? USB_OPERATION_SUCCESS
+                            : USB_OPERATION_ERROR_INTERNAL);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "notifyEnableUsbDataWhileDockedStatus: Failed to call onOperationComplete",
+                        e);
+            }
+        }
     }
 }
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
index 90c8909..abfdd6f 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -158,16 +158,28 @@
      *                 completion.
      * @param callback callback object to be invoked when the operation is complete.
      * @return True when the operation is asynchronous. The caller of
-     *         {@link UsbOperationCallbackInternal} must therefore call
-     *         {@link UsbOperationCallbackInternal#waitForOperationComplete} for processing
+     *         {@link UsbOperationInternal} must therefore call
+     *         {@link UsbOperationInternal#waitForOperationComplete} for processing
      *         the result.
      *         False when the operation is synchronous. Caller can proceed reading the result
-     *         through {@link UsbOperationCallbackInternal#getStatus}
+     *         through {@link UsbOperationInternal#getStatus}
      */
     public boolean enableUsbData(String portName, boolean enable, long transactionId,
             IUsbOperationInternal callback);
 
     /**
+     * Invoked to enable  UsbData when disabled due to docking event.
+     *
+     * @param portName Port Identifier.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     * @param callback callback object to be invoked to invoke the status of the operation upon
+     *                 completion.
+     */
+    public void enableUsbDataWhileDocked(String portName, long transactionId,
+            IUsbOperationInternal callback);
+
+    /**
      * Invoked to enableLimitPowerTransfer on the specified port.
      *
      * @param portName Port Identifier.
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
index 8a0370a..c1d7635 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -30,8 +30,12 @@
 import static android.hardware.usb.UsbPortStatus.MODE_DFP;
 import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
 import static android.hardware.usb.UsbPortStatus.MODE_UFP;
+import static android.hardware.usb.UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_DISABLED_FORCE;
+import static android.hardware.usb.UsbPortStatus.USB_DATA_STATUS_UNKNOWN;
+
 
 import static com.android.server.usb.UsbPortManager.logAndPrint;
 import static com.android.server.usb.UsbPortManager.logAndPrintException;
@@ -40,6 +44,7 @@
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.UsbManager.UsbHalVersion;
 import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
 import android.hardware.usb.V1_0.IUsb;
 import android.hardware.usb.V1_0.PortRoleType;
 import android.hardware.usb.V1_0.Status;
@@ -80,7 +85,7 @@
     private HALCallback mHALCallback;
     private boolean mSystemReady;
     // Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus;
-    private static boolean sUsbDataEnabled = true;
+    private static int sUsbDataStatus = USB_DATA_STATUS_UNKNOWN;
 
     public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
         int version;
@@ -282,6 +287,17 @@
     }
 
     @Override
+    public void enableUsbDataWhileDocked(String portName, long transactionId,
+            IUsbOperationInternal callback) {
+        /* Not supported in HIDL hals*/
+        try {
+            callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "Failed to call onOperationComplete", e);
+        }
+    }
+
+    @Override
     public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long transactionId) {
         synchronized (mLock) {
             if (mProxy == null) {
@@ -332,7 +348,7 @@
                 android.hardware.usb.V1_3.IUsb proxy
                         = android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
                 success = proxy.enableUsbDataSignal(enable);
-            } catch (RemoteException e) {
+           } catch (RemoteException e) {
                 logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId
                         + " portId=" + portName , e);
                 try {
@@ -346,9 +362,8 @@
             }
         }
         if (success) {
-            sUsbDataEnabled = enable;
+            sUsbDataStatus = enable ? USB_DATA_STATUS_UNKNOWN : USB_DATA_STATUS_DISABLED_FORCE;
         }
-
         try {
             callback.onOperationComplete(success
                     ? USB_OPERATION_SUCCESS
@@ -393,8 +408,8 @@
                         current.canChangePowerRole,
                         current.currentDataRole, current.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
-                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled,
-                        false);
+                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus],
+                        false, POWER_BRICK_STATUS_UNKNOWN);
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: "
                         + current.portName);
@@ -427,8 +442,8 @@
                         current.status.canChangePowerRole,
                         current.status.currentDataRole, current.status.canChangeDataRole,
                         false, CONTAMINANT_PROTECTION_NONE,
-                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled,
-                        false);
+                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED, new int[sUsbDataStatus],
+                        false, POWER_BRICK_STATUS_UNKNOWN);
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: "
                         + current.status.portName);
@@ -465,8 +480,8 @@
                         current.contaminantProtectionStatus,
                         current.supportsEnableContaminantPresenceDetection,
                         current.contaminantDetectionStatus,
-                        sUsbDataEnabled,
-                        false);
+                        new int[sUsbDataStatus],
+                        false, POWER_BRICK_STATUS_UNKNOWN);
                 newPortInfo.add(temp);
                 UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: "
                         + current.status_1_1.status.portName);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ce9530c..02c1379 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -43,6 +43,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -571,7 +572,7 @@
         public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x10000000;
 
         //******************************************************************************************
-        // Next CAPABILITY value: 0x20000000
+        // Next CAPABILITY value: 0x40000000
         //******************************************************************************************
 
         /**
@@ -733,6 +734,8 @@
         private final String mContactDisplayName;
         private final @CallDirection int mCallDirection;
         private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+        private final CallEndpoint mActiveCallEndpoint;
+        private final Set<CallEndpoint> mAvailableCallEndpoint;
 
         /**
          * Whether the supplied capabilities  supports the specified capability.
@@ -1116,32 +1119,52 @@
             return mCallerNumberVerificationStatus;
         }
 
+        /**
+         * Return set of available {@link CallEndpoint} which can be used to push or answer this
+         * call via {@link #pushCall(CallEndpoint)} or {@link #answerCall(CallEndpoint, int)}.
+         * @return Set of available call endpoints.
+         */
+        public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+            return mAvailableCallEndpoint;
+        }
+
+        /**
+         * Return the {@link CallEndpoint} which is currently active for a call. If the call does
+         * not take place via any {@link CallEndpoint}, return {@code null}.
+         * @return Current active endpoint.
+         */
+        public @Nullable CallEndpoint getActiveCallEndpoint() {
+            return mActiveCallEndpoint;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (o instanceof Details) {
                 Details d = (Details) o;
                 return
-                        Objects.equals(mState, d.mState) &&
-                        Objects.equals(mHandle, d.mHandle) &&
-                        Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
-                        Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
-                        Objects.equals(mCallerDisplayNamePresentation,
-                                d.mCallerDisplayNamePresentation) &&
-                        Objects.equals(mAccountHandle, d.mAccountHandle) &&
-                        Objects.equals(mCallCapabilities, d.mCallCapabilities) &&
-                        Objects.equals(mCallProperties, d.mCallProperties) &&
-                        Objects.equals(mDisconnectCause, d.mDisconnectCause) &&
-                        Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
-                        Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
-                        Objects.equals(mVideoState, d.mVideoState) &&
-                        Objects.equals(mStatusHints, d.mStatusHints) &&
-                        areBundlesEqual(mExtras, d.mExtras) &&
-                        areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
-                        Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
-                        Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
-                        Objects.equals(mCallDirection, d.mCallDirection) &&
-                        Objects.equals(mCallerNumberVerificationStatus,
-                                d.mCallerNumberVerificationStatus);
+                        Objects.equals(mState, d.mState)
+                                && Objects.equals(mHandle, d.mHandle)
+                                && Objects.equals(mHandlePresentation, d.mHandlePresentation)
+                                && Objects.equals(mCallerDisplayName, d.mCallerDisplayName)
+                                && Objects.equals(mCallerDisplayNamePresentation,
+                                d.mCallerDisplayNamePresentation)
+                                && Objects.equals(mAccountHandle, d.mAccountHandle)
+                                && Objects.equals(mCallCapabilities, d.mCallCapabilities)
+                                && Objects.equals(mCallProperties, d.mCallProperties)
+                                && Objects.equals(mDisconnectCause, d.mDisconnectCause)
+                                && Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis)
+                                && Objects.equals(mGatewayInfo, d.mGatewayInfo)
+                                && Objects.equals(mVideoState, d.mVideoState)
+                                && Objects.equals(mStatusHints, d.mStatusHints)
+                                && areBundlesEqual(mExtras, d.mExtras)
+                                && areBundlesEqual(mIntentExtras, d.mIntentExtras)
+                                && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis)
+                                && Objects.equals(mContactDisplayName, d.mContactDisplayName)
+                                && Objects.equals(mCallDirection, d.mCallDirection)
+                                && Objects.equals(mCallerNumberVerificationStatus,
+                                d.mCallerNumberVerificationStatus)
+                                && Objects.equals(mActiveCallEndpoint, d.mActiveCallEndpoint)
+                                && Objects.equals(mAvailableCallEndpoint, d.mAvailableCallEndpoint);
             }
             return false;
         }
@@ -1190,7 +1213,9 @@
                 long creationTimeMillis,
                 String contactDisplayName,
                 int callDirection,
-                int callerNumberVerificationStatus) {
+                int callerNumberVerificationStatus,
+                CallEndpoint activeCallEndpoint,
+                Set<CallEndpoint> availableCallEndpoints) {
             mState = state;
             mTelecomCallId = telecomCallId;
             mHandle = handle;
@@ -1211,6 +1236,8 @@
             mContactDisplayName = contactDisplayName;
             mCallDirection = callDirection;
             mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+            mActiveCallEndpoint = activeCallEndpoint;
+            mAvailableCallEndpoint = availableCallEndpoints;
         }
 
         /** {@hide} */
@@ -1235,7 +1262,9 @@
                     parcelableCall.getCreationTimeMillis(),
                     parcelableCall.getContactDisplayName(),
                     parcelableCall.getCallDirection(),
-                    parcelableCall.getCallerNumberVerificationStatus());
+                    parcelableCall.getCallerNumberVerificationStatus(),
+                    parcelableCall.getActiveCallEndpoint(),
+                    parcelableCall.getAvailableCallEndpoints());
         }
 
         @Override
@@ -1257,6 +1286,10 @@
             sb.append(capabilitiesToString(mCallCapabilities));
             sb.append(", props: ");
             sb.append(propertiesToString(mCallProperties));
+            sb.append(", activeEndpoint: ");
+            sb.append(mActiveCallEndpoint);
+            sb.append(", availableEndpoints: ");
+            sb.append(mAvailableCallEndpoint);
             sb.append("]");
             return sb.toString();
         }
@@ -1356,6 +1389,121 @@
         public static final int HANDOVER_FAILURE_UNKNOWN = 5;
 
         /**
+         * @hide
+         */
+        @IntDef(prefix = { "PUSH_FAILED_" },
+                value = {PUSH_FAILED_UNKNOWN_REASON, PUSH_FAILED_ENDPOINT_UNAVAILABLE,
+                PUSH_FAILED_ENDPOINT_TIMEOUT, PUSH_FAILED_ENDPOINT_REJECTED})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface PushFailedReason {}
+
+        /**
+         * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when a push
+         * fails due to unknown reason.
+         * <p>
+         * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+         */
+        public static final int PUSH_FAILED_UNKNOWN_REASON = 0;
+
+        /**
+         * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+         * fails due to requested endpoint is unavailable.
+         * <p>
+         * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+         */
+        public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+        /**
+         * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+         * fails due to requested endpoint takes too long to handle the request.
+         * <p>
+         * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+         */
+        public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2;
+
+        /**
+         * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+         * fails due to endpoint rejected the request.
+         * <p>
+         * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+         */
+        public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3;
+
+        /**
+         * @hide
+         */
+        @IntDef(prefix = { "ANSWER_FAILED_" },
+                value = {ANSWER_FAILED_UNKNOWN_REASON, ANSWER_FAILED_ENDPOINT_UNAVAILABLE,
+                        ANSWER_FAILED_ENDPOINT_TIMEOUT, ANSWER_FAILED_ENDPOINT_REJECTED})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface AnswerFailedReason {}
+
+        /**
+         * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+         * fails due to unknown reason.
+         * <p>
+         * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+         */
+        public static final int ANSWER_FAILED_UNKNOWN_REASON = 0;
+
+        /**
+         * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+         * fails due to requested endpoint is unavailable.
+         * <p>
+         * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+         */
+        public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+        /**
+         * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+         * fails due to requested endpoint takes too long to handle the request.
+         * <p>
+         * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+         */
+        public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2;
+
+        /**
+         * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+         * fails due to endpoint rejected the request.
+         * <p>
+         * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+         */
+        public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3;
+
+        /**
+         * @hide
+         */
+        @IntDef(prefix = { "PULL_FAILED_" },
+                value = {PULL_FAILED_UNKNOWN_REASON, PULL_FAILED_ENDPOINT_TIMEOUT,
+                        PULL_FAILED_ENDPOINT_REJECTED})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface PullFailedReason {}
+
+        /**
+         * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+         * unknown reason.
+         * <p>
+         * For more information on pull call, see {@link #pullCall()}.
+         */
+        public static final int PULL_FAILED_UNKNOWN_REASON = 0;
+
+        /**
+         * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+         * requested endpoint takes too long to handle the request.
+         * <p>
+         * For more information on pull call, see {@link #pullCall()}.
+         */
+        public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1;
+
+        /**
+         * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+         * endpoint rejected the request.
+         * <p>
+         * For more information on pull call, see {@link #pullCall()}.
+         */
+        public static final int PULL_FAILED_ENDPOINT_REJECTED = 2;
+
+        /**
          * Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
          *
          * @param call The {@code Call} invoking this method.
@@ -1515,6 +1663,31 @@
          * @param failureReason Error reason for failure.
          */
         public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
+
+        /**
+         * Invoked when call push request via {@link #pushCall(CallEndpoint)} has failed.
+         *
+         * @param endpoint The endpoint requested to push the call to.
+         * @param reason Failed reason.
+         */
+        public void onCallPushFailed(@NonNull CallEndpoint endpoint, @PushFailedReason int reason)
+        {}
+
+        /**
+         * Invoked when answer call request via {@link #answerCall(CallEndpoint, int)} has failed.
+         *
+         * @param endpoint The endpoint requested to answer the call.
+         * @param reason Failed reason
+         */
+        public void onAnswerFailed(@NonNull CallEndpoint endpoint, @AnswerFailedReason int reason)
+        {}
+
+        /**
+         * Invoked when pull call request via {@link #pullCall()} has failed.
+         *
+         * @param reason Failed reason
+         */
+        public void onCallPullFailed(@PullFailedReason int reason) {}
     }
 
     /**
@@ -1936,8 +2109,21 @@
     }
 
     /**
+     * @deprecated Use {@link #pullCall()} instead
+     */
+    @Deprecated
+    public void pullExternalCall() {
+        // If this isn't an external call, ignore the request.
+        if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
+            return;
+        }
+
+        mInCallAdapter.pullExternalCall(mTelecomCallId);
+    }
+
+    /**
      * Initiates a request to the {@link ConnectionService} to pull an external call to the local
-     * device.
+     * device, or to bring a tethered call back to the local device.
      * <p>
      * Calls to this method are ignored if the call does not have the
      * {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} property set.
@@ -1946,13 +2132,34 @@
      * {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
      * in its manifest.
      */
-    public void pullExternalCall() {
-        // If this isn't an external call, ignore the request.
-        if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
-            return;
-        }
+    public void pullCall() {
+        pullExternalCall();
+    }
 
-        mInCallAdapter.pullExternalCall(mTelecomCallId);
+    /**
+     * Initiates a request to the {@link ConnectionService} to push a call to a
+     * {@link CallEndpoint}.
+     * <p>
+     *
+     * @param endpoint The call endpoint to which the call will be pushed.
+     */
+    public void pushCall(@NonNull CallEndpoint endpoint) {
+        mInCallAdapter.pushCall(mTelecomCallId, endpoint);
+    }
+
+    /**
+     * Initiates a request to the {@link ConnectionService} to answer a call to a
+     * {@link CallEndpoint}.
+     * <p>
+     * Calls to this method are ignored if the call does not have the
+     * {@link Call.Details#CAPABILITY_CAN_PULL_CALL} capability set.
+     *
+     * @param endpoint The call endpoint on which to answer the call.
+     * @param videoState The video state in which to answer the call.
+     */
+    public void answerCall(@NonNull CallEndpoint endpoint,
+            @VideoProfile.VideoState int videoState) {
+        mInCallAdapter.answerCall(mTelecomCallId, endpoint, videoState);
     }
 
     /**
@@ -2633,7 +2840,9 @@
                         mDetails.getCreationTimeMillis(),
                         mDetails.getContactDisplayName(),
                         mDetails.getCallDirection(),
-                        mDetails.getCallerNumberVerificationStatus()
+                        mDetails.getCallerNumberVerificationStatus(),
+                        mDetails.getActiveCallEndpoint(),
+                        mDetails.getAvailableCallEndpoints()
                         );
                 fireDetailsChanged(mDetails);
             }
@@ -2675,7 +2884,7 @@
     }
 
     /** {@hide} */
-    final void internalOnHandoverComplete() {
+    void internalOnHandoverComplete() {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final Call call = this;
             final Callback callback = record.getCallback();
@@ -2683,6 +2892,32 @@
         }
     }
 
+    /** {@hide} */
+    void internalOnCallPullFailed(@Callback.PullFailedReason int reason) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onCallPullFailed(reason));
+        }
+    }
+
+    /** {@hide} */
+    void internalOnCallPushFailed(CallEndpoint callEndpoint,
+            @Callback.PushFailedReason int reason) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onCallPushFailed(callEndpoint, reason));
+        }
+    }
+
+    /** {@hide} */
+    void internalOnAnswerFailed(CallEndpoint callEndpoint,
+            @Callback.AnswerFailedReason int reason) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onAnswerFailed(callEndpoint, reason));
+        }
+    }
+
     private void fireStateChanged(final int newState) {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final Call call = this;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/android/telecom/CallEndpoint.aidl
index 2b3e961..5030ffd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.telecom;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
-
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
-}
\ No newline at end of file
+/**
+ * {@hide}
+  */
+parcelable CallEndpoint;
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..dc70656
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.telecom;
+
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents the endpoint on which a call can be carried by the user.
+ *
+ * For example, the user may be able to carry out a call on another device on their local network
+ * using a call streaming solution, or may be able to carry out a call on another device registered
+ * with the same mobile line of service.
+ */
+public final class CallEndpoint implements Parcelable {
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"ENDPOINT_TYPE_"},
+            value = {ENDPOINT_TYPE_TETHERED, ENDPOINT_TYPE_UNTETHERED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EndpointType {}
+
+    /** Indicates the endpoint contains a complete calling stack and is capable of carrying out a
+     *  call on its own. Untethered endpoints are typically other devices which share the same
+     *  mobile line of service as the current device.
+     */
+    public static final int ENDPOINT_TYPE_UNTETHERED = 1;
+
+    /** Indicates the endpoint itself doesn't have the required calling infrastructure in order to
+     *  complete a call on its own. Tethered endpoints depend on a call streaming solution to
+     *  transport the media and control for a call to another device, while depending on the current
+     *  device to connect the call to the mobile network.
+     */
+    public static final int ENDPOINT_TYPE_TETHERED = 2;
+
+    private final ParcelUuid mUuid;
+    private CharSequence mDescription;
+    private final int mType;
+    private final ComponentName mComponentName;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mUuid.writeToParcel(dest, flags);
+        dest.writeCharSequence(mDescription);
+        dest.writeInt(mType);
+        mComponentName.writeToParcel(dest, flags);
+    }
+
+    public static final @android.annotation.NonNull Creator<CallEndpoint> CREATOR =
+            new Creator<CallEndpoint>() {
+                @Override
+                public CallEndpoint createFromParcel(Parcel in) {
+                    return new CallEndpoint(in);
+                }
+
+                @Override
+                public CallEndpoint[] newArray(int size) {
+                    return new CallEndpoint[size];
+                }
+            };
+
+    public CallEndpoint(@NonNull ParcelUuid uuid, @NonNull CharSequence description, int type,
+            @NonNull ComponentName componentName) {
+        mUuid = uuid;
+        mDescription = description;
+        mType = type;
+        mComponentName = componentName;
+    }
+
+    private CallEndpoint(@NonNull Parcel in) {
+        this(ParcelUuid.CREATOR.createFromParcel(in), in.readCharSequence(), in.readInt(),
+                ComponentName.CREATOR.createFromParcel(in));
+    }
+
+    /**
+     * A unique identifier for this call endpoint. An endpoint provider should take care to use an
+     * identifier which is stable for the current association between an endpoint and the current
+     * device, but which is not globally identifying.
+     * @return the unique identifier.
+     */
+    public @NonNull ParcelUuid getIdentifier() {
+        return mUuid;
+    }
+
+    /**
+     * A human-readable description of this {@link CallEndpoint}. An {@link InCallService} uses
+     * when informing the user of the endpoint.
+     * @return the description.
+     */
+    public @NonNull CharSequence getDescription() {
+        return mDescription;
+    }
+
+    public @EndpointType int getType() {
+        return mType;
+    }
+
+    /**
+     * @hide
+     */
+    public @NonNull ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof CallEndpoint) {
+            CallEndpoint d = (CallEndpoint) o;
+            return Objects.equals(mUuid, d.mUuid)
+                    && Objects.equals(mDescription, d.mDescription)
+                    && Objects.equals(mType, d.mType)
+                    && Objects.equals(mComponentName, d.mComponentName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUuid, mDescription, mType, mComponentName);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointCallback.java b/telecomm/java/android/telecom/CallEndpointCallback.java
new file mode 100644
index 0000000..6ba55f1
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.telecom;
+
+/**
+ * Provides callbacks from telecom to the cross device call streaming app with lifecycle events
+ * related to an {@link CallEndpointSession}.
+ */
+public interface CallEndpointCallback {
+    /**
+     * Invoked by telecom when a {@link CallEndpointSession} is started but the streaming app has
+     * not activated the endpoint in a timely manner and the framework deems the activation request
+     * to have timed out.
+     */
+    void onCallEndpointSessionActivationTimeout();
+
+    /**
+     * Invoked by telecom when {@link CallEndpointSession#setCallEndpointSessionDeactivated()}
+     * called by a cross device call streaming app, or when the app uninstalled. When a tethered
+     * {@link CallEndpoint} is deactivated, the call streaming app should clean up any
+     * audio/network resources and stop relaying call controls from the endpoint.
+     */
+    void onCallEndpointSessionDeactivated();
+}
diff --git a/telecomm/java/android/telecom/CallEndpointSession.java b/telecomm/java/android/telecom/CallEndpointSession.java
new file mode 100644
index 0000000..1e7b30c
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointSession.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.telecom;
+
+import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.ICallEndpointSession;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Provides method and necessary information for cross device call streaming app to streams calls
+ * and updates to the status of the endpoint.
+ *
+ */
+public class CallEndpointSession {
+    /**
+     * Indicates that this call endpoint session is activated by
+     * {@link Call#answerCall(CallEndpoint, int)} from the original device.
+     */
+    public static final int ANSWER_REQUEST = 1;
+
+    /**
+     * Indicates that this call endpoint session is activated by {@link Call#pushCall(CallEndpoint)}
+     * from the original device.
+     */
+    public static final int PUSH_REQUEST = 2;
+
+    /**
+     * Indicates that this call endpoint session is activated by
+     * {@link TelecomManager#placeCall(Uri, Bundle)} with extra
+     * {@link TelecomManager#EXTRA_START_CALL_ON_ENDPOINT} set.
+     */
+    public static final int PLACE_REQUEST = 3;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"ACTIVATION_FAILURE_"},
+            value = {ACTIVATION_FAILURE_REJECTED, ACTIVATION_FAILURE_UNAVAILABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActivationFailureReason {}
+    /**
+     * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+     * endpoint is no longer present on the network.
+     */
+    public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0;
+
+    /**
+     * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+     * remote endpoint rejected the request to start streaming a cross device call.
+     */
+    public static final int ACTIVATION_FAILURE_REJECTED = 1;
+
+    private final ICallEndpointSession mCallEndpointSession;
+
+    /**
+     * {@hide}
+     */
+    public CallEndpointSession(ICallEndpointSession callEndpointSession) {
+        mCallEndpointSession = callEndpointSession;
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+     * now activated and that the call is being streamed to the endpoint.
+     */
+    public void setCallEndpointSessionActivated() {
+        try {
+            mCallEndpointSession.setCallEndpointSessionActivated();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint
+     * could not be activated due to error.
+     * Possible errors are:
+     * <ul>
+     *     <li>{@link #ACTIVATION_FAILURE_UNAVAILABLE}</li>
+     *     <li>{@link #ACTIVATION_FAILURE_REJECTED}</li>
+     * </ul>
+     *
+     * @param reason The reason for activation failure
+     */
+    public void setCallEndpointSessionActivationFailed(@ActivationFailureReason int reason) {
+        try {
+            mCallEndpointSession.setCallEndpointSessionActivationFailed(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+     * no longer active.
+     */
+    public void setCallEndpointSessionDeactivated() {
+        try {
+            mCallEndpointSession.setCallEndpointSessionDeactivated();
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 30d4959..21a1804 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -561,15 +561,6 @@
      */
     public static final int PROPERTY_CROSS_SIM = 1 << 13;
 
-    /**
-     * Connection is a tethered external call.
-     * <p>
-     * Indicates that the {@link Connection} is fixed on this device but the audio streams are
-     * re-routed to another device.
-     * <p>
-     */
-    public static final int PROPERTY_TETHERED_CALL = 1 << 14;
-
     //**********************************************************************************************
     // Next PROPERTY value: 1<<14
     //**********************************************************************************************
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..63b9548 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -111,6 +111,22 @@
      */
     public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
 
+    /**
+     * This reason is set when an call is ended due to {@link CallEndpoint} rejection.
+     * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+     * from {@link #getCode()}.
+     */
+    public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+
+    /**
+     * This reason is set when a call is ended due to {@link CallEndpoint} deactivated by
+     * call disconnection or user terminated streaming.
+     * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+     * from {@link #getCode()}
+     */
+    public static final String REASON_ENDPOINT_SESSION_DEACTIVATED =
+            "REASON_ENDPOINT_SESSION_DEACTIVATED";
+
     private int mDisconnectCode;
     private CharSequence mDisconnectLabel;
     private CharSequence mDisconnectDescription;
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..34e9942 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -373,6 +373,34 @@
     }
 
     /**
+     * Instructs Telecom to push a call to the given endpoint.
+     *
+     * @param callId The callId to push.
+     * @param callEndpoint The endpoint to which the call will be pushed.
+     */
+    public void pushCall(String callId, CallEndpoint callEndpoint) {
+        try {
+            mAdapter.pushCall(callId, callEndpoint);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to answer a call via the given endpoint.
+     *
+     * @param callId The callId to push.
+     * @param callEndpoint The endpoint on which the call will be answered.
+     * @param videoState The video state in which to answer the call.
+     */
+    public void answerCall(String callId, CallEndpoint callEndpoint,
+            @VideoProfile.VideoState int videoState) {
+        try {
+            mAdapter.answerCallViaEndpoint(callId, callEndpoint, videoState);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Intructs Telecom to send a call event.
      *
      * @param callId The callId to send the event for.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52d..ecd6596 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -30,9 +30,12 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.view.Surface;
 
 import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
 
@@ -258,6 +261,10 @@
     private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
     private static final int MSG_ON_HANDOVER_FAILED = 12;
     private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+    private static final int MSG_ON_PUSH_FAILED = 14;
+    private static final int MSG_ON_PULL_FAILED = 15;
+    private static final int MSG_ON_ANSWER_EXTERNAL_FAILED = 16;
+    private static final int MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST = 17;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -339,6 +346,66 @@
                     mPhone.internalOnHandoverComplete(callId);
                     break;
                 }
+                case MSG_ON_PUSH_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+                        int reason = (int) args.arg3;
+                        mPhone.internalOnCallPushFailed(callId, callEndpoint, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_PULL_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        int reason = (int) args.arg2;
+                        mPhone.internalOnCallPullFailed(callId, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_ANSWER_EXTERNAL_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String callId = (String) args.arg1;
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+                        int reason = (int) args.arg3;
+                        mPhone.internalOnAnswerFailed(callId, callEndpoint, reason);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg1;
+                        ICallEndpointSession iCallEndpointSession =
+                                (ICallEndpointSession) args.arg2;
+                        try {
+                            mCallEndpointCallback = onCallEndpointActivationRequested(callEndpoint,
+                                    new CallEndpointSession(iCallEndpointSession));
+                        } catch (UnsupportedOperationException e) {
+                            // This InCallService neglected to implement
+                            // onCallEndpointActivationRequested, immediately signal back to Telecom
+                            // that the activation failed.
+                            try {
+                                iCallEndpointSession.setCallEndpointSessionActivationFailed(
+                                        CallEndpointSession.ACTIVATION_FAILURE_UNAVAILABLE);
+                            } catch (RemoteException re) {
+                                // Ignore
+                            }
+                        }
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -353,6 +420,36 @@
         }
 
         @Override
+        public ICallEndpointCallback requestCallEndpointActivation(CallEndpoint callEndpoint,
+                ICallEndpointSession callEndpointSession) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callEndpoint;
+            args.arg2 = callEndpointSession;
+            mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST, args).sendToTarget();
+
+            return new ICallEndpointCallback.Stub() {
+                @Override
+                public void onCallEndpointSessionActivationTimeout() throws RemoteException {
+                    if (mCallEndpointCallback != null) {
+                        mCallEndpointCallback.onCallEndpointSessionActivationTimeout();
+                    }
+                }
+
+                @Override
+                public void onCallEndpointSessionDeactivated() throws RemoteException {
+                    if (mCallEndpointCallback != null) {
+                        mCallEndpointCallback.onCallEndpointSessionDeactivated();
+                    }
+                }
+
+                @Override
+                public IBinder asBinder() {
+                    return this;
+                }
+            };
+        }
+
+        @Override
         public void addCall(ParcelableCall call) {
             mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
         }
@@ -424,6 +521,32 @@
         public void onHandoverComplete(String callId) {
             mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
         }
+
+        @Override
+        public void onCallPullFailed(String callId, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = reason;
+            mHandler.obtainMessage(MSG_ON_PULL_FAILED, args).sendToTarget();
+        }
+
+        @Override
+        public void onCallPushFailed(String callId, CallEndpoint endpoint, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = endpoint;
+            args.arg3 = reason;
+            mHandler.obtainMessage(MSG_ON_PUSH_FAILED, args).sendToTarget();
+        }
+
+        @Override
+        public void onAnswerFailed(String callId, CallEndpoint endpoint, int reason) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = endpoint;
+            args.arg3 = reason;
+            mHandler.obtainMessage(MSG_ON_ANSWER_EXTERNAL_FAILED, args).sendToTarget();
+        }
     }
 
     private Phone.Listener mPhoneListener = new Phone.Listener() {
@@ -470,6 +593,8 @@
     };
 
     private Phone mPhone;
+    private CallEndpointSession mCallEndpointSession;
+    private CallEndpointCallback mCallEndpointCallback;
 
     public InCallService() {
     }
@@ -494,6 +619,14 @@
             onPhoneDestroyed(oldPhone);
         }
 
+        if (mCallEndpointCallback != null) {
+            mCallEndpointCallback = null;
+        }
+
+        if (mCallEndpointSession != null) {
+            mCallEndpointSession = null;
+        }
+
         return false;
     }
 
@@ -704,6 +837,21 @@
     }
 
     /**
+     * To handle the request from telecom to activate an endpoint session. Streaming app with
+     * meta-data {@link TelecomManager#METADATA_STREAMING_TETHERED_CALLS}.
+     * @param endpoint The endpoint which is to be activated.
+     * @param session An instance of {@link CallEndpointSession} to let streaming app report updates
+     *                of the endpoint.
+     * @return CallEndpointCallback The implementation provided by streaming app. Telecom use this
+     *                              to report events related to the call endpoint session.
+     */
+    public @NonNull CallEndpointCallback onCallEndpointActivationRequested(
+            @NonNull CallEndpoint endpoint, @NonNull CallEndpointSession session)
+            throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Used to issue commands to the {@link Connection.VideoProvider} associated with a
      * {@link Call}.
      */
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..c429183 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
@@ -29,8 +30,11 @@
 import com.android.internal.telecom.IVideoProvider;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Information about a call that is used between InCallService and Telecom.
@@ -69,6 +73,8 @@
         private int mCallerNumberVerificationStatus;
         private String mContactDisplayName;
         private String mActiveChildCallId;
+        private CallEndpoint mActiveCallEndpoint;
+        private Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
 
         public ParcelableCallBuilder setId(String id) {
             mId = id;
@@ -224,6 +230,27 @@
             return this;
         }
 
+        /**
+         * Set active call endpoint
+         * @param callEndpoint
+         * @return
+         */
+        public ParcelableCallBuilder setActiveCallEndpoint(CallEndpoint callEndpoint) {
+            mActiveCallEndpoint = callEndpoint;
+            return this;
+        }
+
+        /**
+         * Set available call endpoints
+         * @param availableCallEndpoints
+         * @return
+         */
+        public ParcelableCallBuilder setAvailableCallEndpoints(
+                Set<CallEndpoint> availableCallEndpoints) {
+            mAvailableCallEndpoints = availableCallEndpoints;
+            return this;
+        }
+
         public ParcelableCall createParcelableCall() {
             return new ParcelableCall(
                     mId,
@@ -255,7 +282,9 @@
                     mCallDirection,
                     mCallerNumberVerificationStatus,
                     mContactDisplayName,
-                    mActiveChildCallId);
+                    mActiveChildCallId,
+                    mActiveCallEndpoint,
+                    mAvailableCallEndpoints);
         }
 
         public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +321,8 @@
                     parcelableCall.mCallerNumberVerificationStatus;
             newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
             newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+            newBuilder.mActiveCallEndpoint = parcelableCall.mActiveCallEndpoint;
+            newBuilder.mAvailableCallEndpoints = parcelableCall.mAvailableCallEndpoints;
             return newBuilder;
         }
     }
@@ -327,6 +358,8 @@
     private final int mCallerNumberVerificationStatus;
     private final String mContactDisplayName;
     private final String mActiveChildCallId; // Only valid for CDMA conferences
+    private final CallEndpoint mActiveCallEndpoint;
+    private final Set<CallEndpoint> mAvailableCallEndpoints;
 
     public ParcelableCall(
             String id,
@@ -358,7 +391,9 @@
             int callDirection,
             int callerNumberVerificationStatus,
             String contactDisplayName,
-            String activeChildCallId
+            String activeChildCallId,
+            CallEndpoint activeCallEndpoint,
+            Set<CallEndpoint> availableCallEndpoints
     ) {
         mId = id;
         mState = state;
@@ -390,6 +425,8 @@
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
         mContactDisplayName = contactDisplayName;
         mActiveChildCallId = activeChildCallId;
+        mActiveCallEndpoint = activeCallEndpoint;
+        mAvailableCallEndpoints = availableCallEndpoints;
     }
 
     /** The unique ID of the call. */
@@ -614,6 +651,21 @@
         return mActiveChildCallId;
     }
 
+    /**
+     * @return The {@link CallEndpoint} which is currently active for this call, or null if the call
+     * does not take place via an {@link CallEndpoint}.
+     */
+    public @Nullable CallEndpoint getActiveCallEndpoint() {
+        return mActiveCallEndpoint;
+    }
+
+    /**
+     * @return A set of available {@link CallEndpoint}
+     */
+    public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+        return mAvailableCallEndpoints;
+    }
+
     /** Responsible for creating ParcelableCall objects for deserialized Parcels. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -655,6 +707,9 @@
             int callerNumberVerificationStatus = source.readInt();
             String contactDisplayName = source.readString();
             String activeChildCallId = source.readString();
+            CallEndpoint activeCallEndpoint = source.readParcelable(classLoader);
+            List<CallEndpoint> availablableCallEndpoints = new ArrayList<>();
+            source.readList(availablableCallEndpoints, classLoader);
             return new ParcelableCallBuilder()
                     .setId(id)
                     .setState(state)
@@ -686,6 +741,8 @@
                     .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
                     .setContactDisplayName(contactDisplayName)
                     .setActiveChildCallId(activeChildCallId)
+                    .setActiveCallEndpoint(activeCallEndpoint)
+                    .setAvailableCallEndpoints(new HashSet<>(availablableCallEndpoints))
                     .createParcelableCall();
         }
 
@@ -735,6 +792,8 @@
         destination.writeInt(mCallerNumberVerificationStatus);
         destination.writeString(mContactDisplayName);
         destination.writeString(mActiveChildCallId);
+        destination.writeParcelable(mActiveCallEndpoint, 0);
+        destination.writeList(Arrays.asList(mAvailableCallEndpoints.toArray()));
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..ac91a92 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -292,6 +292,29 @@
         }
     }
 
+    void internalOnCallPullFailed(String callId, @Call.Callback.PullFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnCallPullFailed(reason);
+        }
+    }
+
+    void internalOnAnswerFailed(String callId, CallEndpoint callEndpoint,
+            @Call.Callback.AnswerFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnAnswerFailed(callEndpoint, reason);
+        }
+    }
+
+    void internalOnCallPushFailed(String callId, CallEndpoint callEndpoint,
+            @Call.Callback.PushFailedReason int reason) {
+        Call call = getCallById(callId);
+        if (call != null) {
+            call.internalOnCallPushFailed(callEndpoint, reason);
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6279bf8..e560f34 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -54,8 +54,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 
 /**
@@ -586,6 +588,14 @@
             "android.telecom.extra.START_CALL_WITH_RTT";
 
     /**
+     * A parcelable extra, which when set on the bundle passed into {@link #placeCall(Uri, Bundle)},
+     * indicates that the call should be initiated with an active {@link CallEndpoint} to stream
+     * the call as a tethered call.
+     */
+    public static final String EXTRA_START_CALL_ON_ENDPOINT =
+            "android.telecom.extra.START_CALL_ON_ENDPOINT";
+
+    /**
      * Start an activity indicating that the completion of an outgoing call or an incoming call
      * which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
      * while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
@@ -745,6 +755,23 @@
             "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
 
     /**
+     * A boolean meta-data value indicating this {@link InCallService} implementation is aimed at
+     * working as a streaming app for a tethered call. When there's a tethered call
+     * requesting to a {@link CallEndpoint} registered with this app, Telecom will bind to this
+     * streaming app and let the app streaming the call to the requested endpoint.
+     * <p>
+     * This meta-data can only be set for an {@link InCallService} which doesn't set neither
+     * {@link #METADATA_IN_CALL_SERVICE_UI} nor {@link #METADATA_IN_CALL_SERVICE_CAR_MODE_UI}.
+     * Otherwise, the app will be treated as a phone/dialer app or a car-mode app.
+     * <p>
+     * The {@link InCallService} declared this meta-data must implement
+     * {@link InCallService#onCallEndpointActivationRequested(CallEndpoint, CallEndpointSession)}.
+     * See this method for more information.
+     */
+    public static final String METADATA_STREAMING_TETHERED_CALLS =
+            "android.telecom.STREAMING_TETHERED_CALLS";
+
+    /**
      * The dual tone multi-frequency signaling character sent to indicate the dialing system should
      * pause for a predefined period.
      */
@@ -1294,14 +1321,24 @@
      * {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.
      * <p>
      * Requires permission {@link android.Manifest.permission#READ_PHONE_STATE}, or that the caller
-     * is the default dialer app.
+     * is the default dialer app to get all phone account handles.
+     * <P>
+     * If the caller doesn't meet any of the above requirements and has {@link
+     * android.Manifest.permission#MANAGE_OWN_CALLS}, the caller can get only the phone account
+     * handles they have registered.
      * <p>
-     * A {@link SecurityException} will be thrown if a called is not the default dialer, or lacks
-     * the {@link android.Manifest.permission#READ_PHONE_STATE} permission.
+     * A {@link SecurityException} will be thrown if the caller is not the default dialer
+     * or the caller does not have at least one of the following permissions:
+     * {@link android.Manifest.permission#READ_PHONE_STATE} permission,
+     * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission
      *
      * @return A list of {@code PhoneAccountHandle} objects.
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.MANAGE_OWN_CALLS
+    })
     public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         ITelecomService service = getTelecomService();
         if (service != null) {
@@ -2250,6 +2287,7 @@
      *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
      *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+     *   <li>{@link #EXTRA_START_CALL_ON_ENDPOINT}</li>
      * </ul>
      * <p>
      * An app which implements the self-managed {@link ConnectionService} API uses
@@ -2579,6 +2617,79 @@
         }
     }
 
+    /**
+     * Register a set of {@link CallEndpoint} to telecom. All registered {@link CallEndpoint} can
+     * be provided as options for push, place or answer call externally.
+     *
+     * @param endpoints Endpoints to be registered.
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public void registerCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+        ITelecomService service = getTelecomService();
+        List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+        if (service != null) {
+            try {
+                service.registerCallEndpoints(endpointList, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+    }
+
+    /**
+     * Unregister all {@link CallEndpoint} from telecom in the set provided. After un-registration,
+     * telecom will stop tracking and maintaining these {@link CallEndpoint}, user can no longer
+     * carry a call on them.
+     *
+     * @param endpoints
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public void unregisterCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+        ITelecomService service = getTelecomService();
+        List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+        if (service != null) {
+            try {
+                service.unregisterCallEndpoints(endpointList, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException unregisterCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+    }
+
+    /**
+     * Return a set all registered {@link CallEndpoint} that can be used to stream and carry an
+     * external call.
+     *
+     * @return A set of all available {@link CallEndpoint}.
+     */
+    // TODO: add permission requirements
+    // @RequiresPermission{}
+    public @NonNull Set<CallEndpoint> getCallEndpoints() {
+        Set<CallEndpoint> endpoints = new HashSet<>();
+        List<CallEndpoint> endpointList;
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                endpointList = service.getCallEndpoints(mContext.getOpPackageName());
+                return new HashSet<>(endpointList);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+                e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is null.");
+        }
+        return endpoints;
+    }
+
     private boolean isSystemProcess() {
         return Process.myUid() == Process.SYSTEM_UID;
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
index 2b3e961..dc1cc0f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package com.android.internal.telecom;
 
-import com.android.systemui.shared.system.smartspace.ISmartspaceCallback;
+/**
+ * Internal remote CallEndpointCallback interface for Telecom framework to report event related to
+ * the endpoint session.
+ *
+ * {@hide}
+ */
+oneway interface ICallEndpointCallback {
+    void onCallEndpointSessionActivationTimeout();
 
-// Controller that keeps track of SmartSpace instances in remote processes (such as Launcher).
-interface ISmartspaceTransitionController {
-    oneway void setSmartspace(ISmartspaceCallback callback);
+    void onCallEndpointSessionDeactivated();
 }
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
new file mode 100644
index 0000000..1c1c29a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.telecom;
+
+/**
+ * Internal remote CallEndpointSession interface for streaming app to update the status of the
+ * endpoint.
+ *
+ * @see android.telecom.CallEndpointSession
+ *
+ * {@hide}
+ */
+
+oneway interface ICallEndpointSession {
+    void setCallEndpointSessionActivated();
+
+    void setCallEndpointSessionActivationFailed(int reason);
+
+    void setCallEndpointSessionDeactivated();
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..986871f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..ecca835 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecom.CallEndpoint;
 import android.telecom.PhoneAccountHandle;
 
 /**
@@ -95,4 +96,8 @@
 
     void handoverTo(String callId, in PhoneAccountHandle destAcct, int videoState,
             in Bundle extras);
+
+    void pushCall(String callId, in CallEndpoint endpoint);
+
+    void answerCallViaEndpoint(String callId, in CallEndpoint endpoint, int videoState);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..93d9f28 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,9 +19,12 @@
 import android.app.PendingIntent;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ParcelableCall;
 
 import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
 
 /**
  * Internal remote interface for in-call services.
@@ -30,9 +33,12 @@
  *
  * {@hide}
  */
-oneway interface IInCallService {
+interface IInCallService {
     void setInCallAdapter(in IInCallAdapter inCallAdapter);
 
+    ICallEndpointCallback requestCallEndpointActivation(in CallEndpoint callEndpoint,
+        in ICallEndpointSession callEndpointSession);
+
     void addCall(in ParcelableCall call);
 
     void updateCall(in ParcelableCall call);
@@ -58,4 +64,10 @@
     void onHandoverFailed(String callId, int error);
 
     void onHandoverComplete(String callId);
+
+    void onCallPullFailed(String callId, int reason);
+
+    void onCallPushFailed(String callId, in CallEndpoint endpoint, int reason);
+
+    void onAnswerFailed(String callId, in CallEndpoint endpoint, int reason);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index b9936ce..985f6bc 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.telecom.CallEndpoint;
 import android.telecom.TelecomAnalytics;
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
@@ -368,4 +369,19 @@
      * @see TelecomServiceImpl#setTestCallDiagnosticService
      */
     void setTestCallDiagnosticService(in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#registerCallEndpoints(in List<CallEndpoint>, in String);
+     */
+    void registerCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#unregisterCallEndpoints(in List<CallEndpoint>, String);
+     */
+    void unregisterCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#getCallEndpoints(in String packageName);
+     */
+    List<CallEndpoint> getCallEndpoints(in String packageName);
 }
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 61de3ac..154bb11 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -268,6 +268,13 @@
     @SystemApi
     public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
 
+    /**
+     * A capability update has been requested due to IMS being registered over INTERNET PDN.
+     * @hide
+     */
+    @SystemApi
+    public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN = 12;
+
     /**@hide*/
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "ERROR_", value = {
@@ -282,7 +289,8 @@
             CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN,
             CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN,
             CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED,
-            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED,
+            CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN
     })
     public @interface StackPublishTriggerType {}
 
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index af7373b..70e4ef1 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -488,9 +488,7 @@
      * has already been created in the framework.
      * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange
      * event to the framework.
-     * @hide
      */
-    @SystemApi
     private void initRcsCapabilityExchangeImplBase(
             @NonNull CapabilityExchangeEventListener listener) {
         synchronized (mLock) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index f2696d8..815ea77 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -178,7 +178,7 @@
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
-                .getConfigNonRotationTests(repetitions = 1,
+                .getConfigNonRotationTests(repetitions = 5,
                     // b/190352379 (IME doesn't show on app launch in 90 degrees)
                     supportedRotations = listOf(Surface.ROTATION_0),
                     supportedNavigationModes = listOf(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 5450610..d38a719 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -25,9 +25,7 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,11 +81,7 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index aac4812..7443f0b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -28,9 +28,7 @@
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -100,47 +98,12 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun entireScreenCovered() {
-        // This test doesn't work in shell transitions because of b/204570898
-        assumeFalse(isShellTransitionsEnabled)
-        super.entireScreenCovered()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() {
-        // This test doesn't work in shell transitions because of b/206085788
-        assumeFalse(isShellTransitionsEnabled)
-        super.appWindowReplacesLauncherAsTopWindow()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerReplacesLauncher() {
-        // This test doesn't work in shell transitions because of b/206085788
-        assumeFalse(isShellTransitionsEnabled)
-        super.appLayerReplacesLauncher()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        // This test doesn't work in shell transitions because of b/206090480
-        assumeFalse(isShellTransitionsEnabled)
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
 
     /** {@inheritDoc} */
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index a85dcc5..12177ed 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -28,11 +28,9 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.google.common.truth.Truth
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -181,18 +179,12 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 202936526)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
     fun statusBarLayerPositionAtEnd() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersEnd {
             val display = this.entry.displays.minByOrNull { it.id }
                 ?: error("There is no display!")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index cd209b2..304e516 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -25,8 +25,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -87,11 +85,7 @@
     /** {@inheritDoc} */
     @FlakyTest(bugId = 206753786)
     @Test
-    override fun statusBarLayerRotatesScales() {
-        // This test doesn't work in shell transitions because of b/206753786
-        assumeFalse(isShellTransitionsEnabled)
-        super.statusBarLayerRotatesScales()
-    }
+    override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc} */
     @Presubmit