Merge "Fixed several data issues"
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 77b26f8..8a15758 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -32,23 +32,15 @@
 }
 
 /////////////////////////////////////////////////////////////////////
-// Common metalava configs
-/////////////////////////////////////////////////////////////////////
-
-stubs_defaults {
-    name: "metalava-non-updatable-api-stubs-default",
-    defaults: ["android-non-updatable-stubs-defaults"],
-    api_levels_annotations_enabled: false,
-    defaults_visibility: ["//visibility:private"],
-}
-
-/////////////////////////////////////////////////////////////////////
 // These modules provide source files for the stub libraries
 /////////////////////////////////////////////////////////////////////
 
 droidstubs {
     name: "api-stubs-docs-non-updatable",
-    defaults: ["metalava-non-updatable-api-stubs-default"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args,
     check_api: {
         current: {
@@ -97,7 +89,10 @@
 
 droidstubs {
     name: "system-api-stubs-docs-non-updatable",
-    defaults: ["metalava-non-updatable-api-stubs-default"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args + priv_apps,
     check_api: {
         current: {
@@ -133,7 +128,10 @@
 
 droidstubs {
     name: "test-api-stubs-docs-non-updatable",
-    defaults: ["metalava-non-updatable-api-stubs-default"],
+    defaults: [
+        "android-non-updatable-stubs-defaults",
+        "module-classpath-stubs-defaults",
+    ],
     args: metalava_framework_docs_args + test + priv_apps_in_stubs,
     check_api: {
         current: {
@@ -175,7 +173,10 @@
 
 droidstubs {
     name: "module-lib-api-stubs-docs-non-updatable",
-    defaults: ["metalava-non-updatable-api-stubs-default"],
+    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/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
index cf94e9e..4cd9741 100644
--- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
@@ -25,7 +25,6 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
-import android.graphics.Rect;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.view.inputmethod.EditorInfo;
@@ -190,7 +189,7 @@
         final View view = new View(mContext);
         final EditorInfo editorInfo = new EditorInfo();
         while (state.keepRunning()) {
-            mHandwritingInitiator.onInputConnectionCreated(view, editorInfo);
+            mHandwritingInitiator.onInputConnectionCreated(view);
             state.pauseTiming();
             mHandwritingInitiator.onInputConnectionClosed(view);
             state.resumeTiming();
@@ -201,24 +200,14 @@
     public void onInputConnectionClosed() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final View view = new View(mContext);
-        final EditorInfo editorInfo = new EditorInfo();
         while (state.keepRunning()) {
             state.pauseTiming();
-            mHandwritingInitiator.onInputConnectionCreated(view, editorInfo);
+            mHandwritingInitiator.onInputConnectionCreated(view);
             state.resumeTiming();
             mHandwritingInitiator.onInputConnectionClosed(view);
         }
     }
 
-    @Test
-    public void updateEditorBoundary() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final Rect rect = new Rect(0, 0, 100, 100);
-        while (state.keepRunning()) {
-            mHandwritingInitiator.updateEditorBound(rect);
-        }
-    }
-
     private MotionEvent createMotionEvent(int action, int toolType, int x, int y, long eventTime) {
         MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
         properties[0].toolType = toolType;
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/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
index ba92d95..73ef310 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -48,6 +48,7 @@
         mLeaseInfos = leaseInfos;
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     private BlobInfo(Parcel in) {
         mId = in.readLong();
         mExpiryTimeMs = in.readLong();
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 9c0c365..66767e2 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -1408,6 +1408,7 @@
          * Use the {@link #CREATOR}
          * @hide
          */
+        @SuppressWarnings("UnsafeParcelApi")
         AlarmClockInfo(Parcel in) {
             mTriggerTime = in.readLong();
             mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader());
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 0e6006a..b9673f2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -881,6 +881,7 @@
         return hashCode;
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     private JobInfo(Parcel in) {
         jobId = in.readInt();
         extras = in.readPersistableBundle();
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/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 4de8ec8..dd102bd 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -892,7 +892,8 @@
                 }
                 if (history.bucketExpiryTimesMs != null) {
                     xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES);
-                    for (int j = 0; j < history.bucketExpiryTimesMs.size(); ++j) {
+                    final int size = history.bucketExpiryTimesMs.size();
+                    for (int j = 0; j < size; ++j) {
                         final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j);
                         // Skip writing to disk if the expiry time already elapsed.
                         if (expiryTimeMs < elapsedTimeMs) {
@@ -994,7 +995,8 @@
             return;
         }
         idpw.print("(");
-        for (int i = 0; i < appUsageHistory.bucketExpiryTimesMs.size(); ++i) {
+        final int size = appUsageHistory.bucketExpiryTimesMs.size();
+        for (int i = 0; i < size; ++i) {
             final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i);
             final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i);
             if (i != 0) {
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 ee09952..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 : 45 * ONE_DAY
+            COMPRESS_TIME ? 32 * ONE_MINUTE : 8 * ONE_DAY
     };
 
     /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */
@@ -2540,7 +2540,7 @@
                     switch (name) {
                         case KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS:
                             mInjector.mAutoRestrictedBucketDelayMs = Math.max(
-                                    COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR,
+                                    COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR,
                                     properties.getLong(KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS,
                                             DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS));
                             break;
diff --git a/apex/media/framework/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java
index f6fd509..9f80c43 100644
--- a/apex/media/framework/java/android/media/MediaSession2Service.java
+++ b/apex/media/framework/java/android/media/MediaSession2Service.java
@@ -161,19 +161,19 @@
     public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo);
 
     /**
-     * Called when notification UI needs update. Override this method to show or cancel your own
-     * notification UI.
+     * Called to update the media notification when the playback state changes.
      * <p>
-     * This would be called on {@link MediaSession2}'s callback executor when playback state is
-     * changed.
+     * If playback is active and a notification is returned, the service uses it to become a
+     * foreground service. If playback is not active then the notification is still posted, but the
+     * service does not become a foreground service.
      * <p>
-     * With the notification returned here, the service becomes foreground service when the playback
-     * is started. Apps must request the permission
-     * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes
-     * background service after the playback is stopped.
+     * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission
+     * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU}
+     * or later, notifications will only be posted if the app has also been granted the
+     * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission.
      *
-     * @param session a session that needs notification update.
-     * @return a {@link MediaNotification}. Can be {@code null}.
+     * @param session the session for which an updated media notification is required.
+     * @return the {@link MediaNotification}. Can be {@code null}.
      */
     @Nullable
     public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session);
diff --git a/api/Android.bp b/api/Android.bp
index 362f39f..a22c2f6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -133,6 +133,7 @@
     system_server_classpath: [
         "service-media-s",
         "service-permission",
+        "service-supplementalprocess",
     ],
 }
 
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index a157517..6a685a79 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 06e3bd0..7363c26 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -125,13 +125,18 @@
     field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+    field public static final String READ_ASSISTANT_APP_SEARCH_DATA = "android.permission.READ_ASSISTANT_APP_SEARCH_DATA";
     field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
     field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
+    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";
@@ -213,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";
@@ -824,6 +831,7 @@
     field public static final int indicatorRight = 16843022; // 0x101010e
     field public static final int indicatorStart = 16843729; // 0x10103d1
     field public static final int inflatedId = 16842995; // 0x10100f3
+    field public static final int inheritKeyStoreKeys;
     field public static final int inheritShowWhenLocked = 16844188; // 0x101059c
     field public static final int initOrder = 16842778; // 0x101001a
     field public static final int initialKeyguardLayout = 16843714; // 0x10103c2
@@ -950,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
@@ -1132,6 +1141,7 @@
     field public static final int popupWindowStyle = 16842870; // 0x1010076
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
+    field public static final int preferKeepClear;
     field public static final int preferMinimalPostProcessing = 16844300; // 0x101060c
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
     field public static final int preferenceFragmentStyle = 16844038; // 0x1010506
@@ -3995,7 +4005,7 @@
     method @Deprecated public void onTabUnselected(android.app.ActionBar.Tab, android.app.FragmentTransaction);
   }
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     ctor public Activity();
     method public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public void closeContextMenu();
@@ -4038,6 +4048,7 @@
     method public int getMaxNumPictureInPictureActions();
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
+    method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
@@ -4916,7 +4927,7 @@
     method public void onDateSet(android.widget.DatePicker, int, int, int);
   }
 
-  public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  public class Dialog implements android.content.DialogInterface android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     ctor public Dialog(@NonNull @UiContext android.content.Context);
     ctor public Dialog(@NonNull @UiContext android.content.Context, @StyleRes int);
     ctor protected Dialog(@NonNull @UiContext android.content.Context, boolean, @Nullable android.content.DialogInterface.OnCancelListener);
@@ -4936,6 +4947,7 @@
     method @NonNull @UiContext public final android.content.Context getContext();
     method @Nullable public android.view.View getCurrentFocus();
     method @NonNull public android.view.LayoutInflater getLayoutInflater();
+    method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method @Nullable public final android.app.Activity getOwnerActivity();
     method @Nullable public final android.view.SearchEvent getSearchEvent();
     method public final int getVolumeControlStream();
@@ -7289,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 @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @Nullable public android.graphics.drawable.Drawable getDrawable(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(int, int, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
-    method @Nullable 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);
@@ -7309,6 +7321,7 @@
     method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
     method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
+    method public int getMinimumRequiredWifiSecurityLevel();
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy();
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy();
     method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
@@ -7335,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);
@@ -7349,6 +7363,7 @@
     method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName);
     method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName);
     method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName);
+    method @Nullable public android.app.admin.WifiSsidPolicy getWifiSsidPolicy();
     method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
     method public boolean grantKeyPairToWifiAuth(@NonNull String);
     method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
@@ -7453,6 +7468,7 @@
     method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int);
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
     method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
+    method public void setMinimumRequiredWifiSecurityLevel(int);
     method public void setNearbyAppStreamingPolicy(int);
     method public void setNearbyNotificationStreamingPolicy(int);
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
@@ -7477,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);
@@ -7501,6 +7518,7 @@
     method public void setUsbDataSignalingEnabled(boolean);
     method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap);
+    method public void setWifiSsidPolicy(@Nullable android.app.admin.WifiSsidPolicy);
     method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method public int stopUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method public boolean switchUser(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle);
@@ -7552,6 +7570,7 @@
     field public static final String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
     field public static final String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
     field public static final String EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES = "android.app.extra.PROVISIONING_ALLOWED_PROVISIONING_MODES";
+    field public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE = "android.app.extra.PROVISIONING_ALLOW_OFFLINE";
     field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME";
     field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE = "android.app.extra.PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE";
     field public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM";
@@ -7595,6 +7614,7 @@
     field public static final String EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE = "android.app.extra.PROVISIONING_WIFI_USER_CERTIFICATE";
     field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID";
     field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE";
+    field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING";
     field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1
     field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
     field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
@@ -7669,6 +7689,10 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
+    field public static final int WIFI_SECURITY_ENTERPRISE_192 = 3; // 0x3
+    field public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2; // 0x2
+    field public static final int WIFI_SECURITY_OPEN = 0; // 0x0
+    field public static final int WIFI_SECURITY_PERSONAL = 1; // 0x1
     field public static final int WIPE_EUICC = 4; // 0x4
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7693,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 {
@@ -7755,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
@@ -7856,6 +7906,18 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
   }
 
+  public final class WifiSsidPolicy implements android.os.Parcelable {
+    method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<java.lang.String>);
+    method public int describeContents();
+    method public int getPolicyType();
+    method @NonNull public java.util.Set<java.lang.String> getSsids();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.WifiSsidPolicy> CREATOR;
+    field public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; // 0x0
+    field public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; // 0x1
+  }
+
 }
 
 package android.app.assist {
@@ -8838,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);
@@ -8848,11 +8911,12 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
     method public boolean isEnabled();
     method public boolean isLe2MPhySupported();
+    method public int isLeAudioBroadcastAssistantSupported();
+    method public int isLeAudioBroadcastSourceSupported();
     method public int isLeAudioSupported();
     method public boolean isLeCodedPhySupported();
     method public boolean isLeExtendedAdvertisingSupported();
     method public boolean isLePeriodicAdvertisingSupported();
-    method public int isLePeriodicAdvertisingSyncTransferSenderSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -9261,6 +9325,7 @@
     field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
     field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
     field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LC3 = 5; // 0x5
     field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
     field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
   }
@@ -9353,6 +9418,7 @@
     field public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
     field public static final String EXTRA_PREVIOUS_BOND_STATE = "android.bluetooth.device.extra.PREVIOUS_BOND_STATE";
     field public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
+    field public static final String EXTRA_TRANSPORT = "android.bluetooth.device.extra.TRANSPORT";
     field public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
     field public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; // 0x2
     field public static final int PAIRING_VARIANT_PIN = 0; // 0x0
@@ -9715,24 +9781,61 @@
     method public void close();
     method protected void finalize();
     method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothDevice getConnectedGroupLeadDevice(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice);
     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 {
@@ -9753,6 +9856,7 @@
     field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
     field public static final int GATT = 7; // 0x7
     field public static final int GATT_SERVER = 8; // 0x8
+    field public static final int HAP_CLIENT = 28; // 0x1c
     field public static final int HEADSET = 1; // 0x1
     field @Deprecated public static final int HEALTH = 3; // 0x3
     field public static final int HEARING_AID = 21; // 0x15
@@ -10928,10 +11032,10 @@
     method @Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void removeStickyBroadcast(@RequiresPermission android.content.Intent);
     method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
+    method public void revokeOwnPermissionOnKill(@NonNull String);
+    method public void revokeOwnPermissionsOnKill(@NonNull java.util.Collection<java.lang.String>);
     method public abstract void revokeUriPermission(android.net.Uri, int);
     method public abstract void revokeUriPermission(String, android.net.Uri, int);
-    method public void selfRevokePermission(@NonNull String);
-    method public void selfRevokePermissions(@NonNull java.util.Collection<java.lang.String>);
     method public abstract void sendBroadcast(@RequiresPermission android.content.Intent);
     method public abstract void sendBroadcast(@RequiresPermission android.content.Intent, @Nullable String);
     method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle);
@@ -11059,8 +11163,8 @@
     field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
     field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
     field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
-    field public static final String TV_IAPP_SERVICE = "tv_iapp";
     field public static final String TV_INPUT_SERVICE = "tv_input";
+    field public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app";
     field public static final String UI_MODE_SERVICE = "uimode";
     field public static final String USAGE_STATS_SERVICE = "usagestats";
     field public static final String USB_SERVICE = "usb";
@@ -13179,6 +13283,7 @@
     field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
     field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
     field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+    field public static final String FEATURE_WINDOW_MAGNIFICATION = "android.software.window_magnification";
     field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
     field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
     field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -13471,6 +13576,7 @@
   public final class ShortcutInfo implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.content.ComponentName getActivity();
+    method @NonNull public java.util.List<java.lang.String> getCapabilityParameterValues(@NonNull String, @NonNull String);
     method @Nullable public java.util.Set<java.lang.String> getCategories();
     method @Nullable public CharSequence getDisabledMessage();
     method public int getDisabledReason();
@@ -13485,6 +13591,7 @@
     method public int getRank();
     method @Nullable public CharSequence getShortLabel();
     method public android.os.UserHandle getUserHandle();
+    method public boolean hasCapability(@NonNull String);
     method public boolean hasKeyFieldsOnly();
     method public boolean isCached();
     method public boolean isDeclaredInManifest();
@@ -13509,6 +13616,7 @@
 
   public static class ShortcutInfo.Builder {
     ctor public ShortcutInfo.Builder(android.content.Context, String);
+    method @NonNull public android.content.pm.ShortcutInfo.Builder addCapabilityBinding(@NonNull String, @Nullable String, @Nullable java.util.List<java.lang.String>);
     method @NonNull public android.content.pm.ShortcutInfo build();
     method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
@@ -17582,12 +17690,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 {
@@ -18043,6 +18155,7 @@
     field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
     field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
     field public static final long USAGE_CPU_WRITE_RARELY = 32L; // 0x20L
+    field public static final long USAGE_FRONT_BUFFER = 1L; // 0x1L
     field public static final long USAGE_GPU_COLOR_OUTPUT = 512L; // 0x200L
     field public static final long USAGE_GPU_CUBE_MAP = 33554432L; // 0x2000000L
     field public static final long USAGE_GPU_DATA_BUFFER = 16777216L; // 0x1000000L
@@ -20919,6 +21032,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
@@ -22069,14 +22183,30 @@
   public class ImageWriter implements java.lang.AutoCloseable {
     method public void close();
     method public android.media.Image dequeueInputImage();
+    method public long getDataSpace();
     method public int getFormat();
+    method public int getHardwareBufferFormat();
+    method public int getHeight();
     method public int getMaxImages();
+    method public long getUsage();
+    method public int getWidth();
     method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int);
     method @NonNull public static android.media.ImageWriter newInstance(@NonNull android.view.Surface, @IntRange(from=1) int, int);
     method public void queueInputImage(android.media.Image);
     method public void setOnImageReleasedListener(android.media.ImageWriter.OnImageReleasedListener, android.os.Handler);
   }
 
+  public static final class ImageWriter.Builder {
+    ctor public ImageWriter.Builder(@NonNull android.view.Surface);
+    method @NonNull public android.media.ImageWriter build();
+    method @NonNull public android.media.ImageWriter.Builder setDataSpace(long);
+    method @NonNull public android.media.ImageWriter.Builder setHardwareBufferFormat(int);
+    method @NonNull public android.media.ImageWriter.Builder setImageFormat(int);
+    method @NonNull public android.media.ImageWriter.Builder setMaxImages(@IntRange(from=1) int);
+    method @NonNull public android.media.ImageWriter.Builder setUsage(long);
+    method @NonNull public android.media.ImageWriter.Builder setWidthAndHeight(@IntRange(from=1) int, @IntRange(from=1) int);
+  }
+
   public static interface ImageWriter.OnImageReleasedListener {
     method public void onImageReleased(android.media.ImageWriter);
   }
@@ -22488,6 +22618,7 @@
     field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final String FEATURE_AdaptivePlayback = "adaptive-playback";
     field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp";
+    field public static final String FEATURE_EncodingStatistics = "encoding-statistics";
     field public static final String FEATURE_FrameParsing = "frame-parsing";
     field public static final String FEATURE_IntraRefresh = "intra-refresh";
     field public static final String FEATURE_LowLatency = "low-latency";
@@ -23262,6 +23393,7 @@
     field public static final String KEY_OPERATING_RATE = "operating-rate";
     field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
     field public static final String KEY_PCM_ENCODING = "pcm-encoding";
+    field public static final String KEY_PICTURE_TYPE = "picture-type";
     field public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
     field public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
     field public static final String KEY_PREPEND_HEADER_TO_SYNC_FRAMES = "prepend-sps-pps-to-idr-frames";
@@ -23279,6 +23411,8 @@
     field public static final String KEY_TILE_HEIGHT = "tile-height";
     field public static final String KEY_TILE_WIDTH = "tile-width";
     field public static final String KEY_TRACK_ID = "track-id";
+    field public static final String KEY_VIDEO_ENCODING_STATISTICS_LEVEL = "video-encoding-statistics-level";
+    field public static final String KEY_VIDEO_QP_AVERAGE = "video-qp-average";
     field public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max";
     field public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min";
     field public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max";
@@ -23339,12 +23473,18 @@
     field public static final String MIMETYPE_VIDEO_SCRAMBLED = "video/scrambled";
     field public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     field public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    field public static final int PICTURE_TYPE_B = 3; // 0x3
+    field public static final int PICTURE_TYPE_I = 1; // 0x1
+    field public static final int PICTURE_TYPE_P = 2; // 0x2
+    field public static final int PICTURE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int TYPE_BYTE_BUFFER = 5; // 0x5
     field public static final int TYPE_FLOAT = 3; // 0x3
     field public static final int TYPE_INTEGER = 1; // 0x1
     field public static final int TYPE_LONG = 2; // 0x2
     field public static final int TYPE_NULL = 0; // 0x0
     field public static final int TYPE_STRING = 4; // 0x4
+    field public static final int VIDEO_ENCODING_STATISTICS_LEVEL_1 = 1; // 0x1
+    field public static final int VIDEO_ENCODING_STATISTICS_LEVEL_NONE = 0; // 0x0
   }
 
   public final class MediaMetadata implements android.os.Parcelable {
@@ -25707,6 +25847,7 @@
 
   public final class MidiDeviceInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public int getDefaultProtocol();
     method public int getId();
     method public int getInputPortCount();
     method public int getOutputPortCount();
@@ -25723,6 +25864,14 @@
     field public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
     field public static final String PROPERTY_USB_DEVICE = "usb_device";
     field public static final String PROPERTY_VERSION = "version";
+    field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; // 0x3
+    field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; // 0x4
+    field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; // 0x1
+    field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; // 0x2
+    field public static final int PROTOCOL_UMP_MIDI_2_0 = 17; // 0x11
+    field public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; // 0x12
+    field public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; // 0x0
+    field public static final int PROTOCOL_UNKNOWN = -1; // 0xffffffff
     field public static final int TYPE_BLUETOOTH = 3; // 0x3
     field public static final int TYPE_USB = 1; // 0x1
     field public static final int TYPE_VIRTUAL = 2; // 0x2
@@ -25763,11 +25912,15 @@
   }
 
   public final class MidiManager {
-    method public android.media.midi.MidiDeviceInfo[] getDevices();
+    method @Deprecated public android.media.midi.MidiDeviceInfo[] getDevices();
+    method @NonNull public java.util.Set<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int);
     method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
     method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
-    method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
+    method @Deprecated public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
+    method public void registerDeviceCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.midi.MidiManager.DeviceCallback);
     method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
+    field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1
+    field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2
   }
 
   public static class MidiManager.DeviceCallback {
@@ -26095,6 +26248,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...);
@@ -26665,12 +26827,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);
@@ -26684,6 +26848,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);
@@ -26785,6 +26950,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);
@@ -26811,6 +26977,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);
@@ -26828,16 +26995,93 @@
 
 package android.media.tv.interactive {
 
-  public final class TvIAppManager {
+  public final class TvInteractiveAppInfo implements android.os.Parcelable {
+    ctor public TvInteractiveAppInfo(@NonNull android.content.Context, @NonNull android.content.ComponentName);
+    method public int describeContents();
+    method @NonNull public String getId();
+    method @Nullable public android.content.pm.ServiceInfo getServiceInfo();
+    method @NonNull public int getSupportedTypes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.interactive.TvInteractiveAppInfo> CREATOR;
+    field public static final int INTERACTIVE_APP_TYPE_ATSC = 2; // 0x2
+    field public static final int INTERACTIVE_APP_TYPE_GINGA = 4; // 0x4
+    field public static final int INTERACTIVE_APP_TYPE_HBBTV = 1; // 0x1
   }
 
-  public abstract class TvIAppService extends android.app.Service {
-    ctor public TvIAppService();
+  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);
-    field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+    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);
+  }
+
 }
 
 package android.mtp {
@@ -32037,6 +32281,7 @@
     method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale);
     method public boolean isEmpty();
     method public static boolean isPseudoLocale(@Nullable android.icu.util.ULocale);
+    method public static boolean matchesLanguageAndScript(@NonNull java.util.Locale, @NonNull java.util.Locale);
     method public static void setDefault(@NonNull @Size(min=1) android.os.LocaleList);
     method @IntRange(from=0) public int size();
     method @NonNull public String toLanguageTags();
@@ -32214,7 +32459,7 @@
     method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
     method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
-    method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<? super T>);
     method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
     method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
@@ -32224,7 +32469,7 @@
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
     method @Deprecated @Nullable public java.io.Serializable readSerializable();
-    method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Nullable public <T extends java.io.Serializable> T readSerializable(@Nullable ClassLoader, @NonNull Class<? super T>);
     method @NonNull public android.util.Size readSize();
     method @NonNull public android.util.SizeF readSizeF();
     method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
@@ -32494,6 +32739,7 @@
     method public static final boolean is64Bit();
     method public static boolean isApplicationUid(int);
     method public static final boolean isIsolated();
+    method public static final boolean isSupplemental();
     method public static final void killProcess(int);
     method public static final int myPid();
     method @NonNull public static String myProcessName();
@@ -32934,9 +33180,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);
@@ -33262,6 +33512,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 {
@@ -40412,6 +40673,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();
@@ -40432,7 +40694,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);
@@ -40475,7 +40739,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>);
@@ -40491,11 +40758,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 {
@@ -40503,6 +40781,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();
@@ -40596,6 +40876,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();
@@ -40865,7 +41173,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
@@ -40999,6 +41306,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
@@ -41027,6 +41336,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);
@@ -41289,11 +41599,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();
@@ -41309,10 +41620,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";
@@ -41356,6 +41669,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";
@@ -41367,6 +41681,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
@@ -41733,11 +42048,11 @@
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
-    field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
+    field @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
     field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
     field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
-    field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
-    field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
+    field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
+    field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
     field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
     field public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
     field @Deprecated public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
@@ -41983,6 +42298,13 @@
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1
     field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0
+    field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array";
+    field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array";
     field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool";
     field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool";
     field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool";
@@ -41997,11 +42319,13 @@
     field public static final String KEY_IPV4_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv4_sip_mtu_size_cellular_int";
     field public static final String KEY_IPV6_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv6_sip_mtu_size_cellular_int";
     field public static final String KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL = "ims.keep_pdn_up_in_no_vops_bool";
+    field public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE = "ims.mmtel_requires_provisioning_bundle";
     field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int";
     field public static final String KEY_PHONE_CONTEXT_DOMAIN_NAME_STRING = "ims.phone_context_domain_name_string";
     field public static final String KEY_PREFIX = "ims.";
     field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool";
     field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array";
+    field public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = "ims.rcs_requires_provisioning_bundle";
     field public static final String KEY_REGISTRATION_EVENT_PACKAGE_SUPPORTED_BOOL = "ims.registration_event_package_supported_bool";
     field public static final String KEY_REGISTRATION_EXPIRY_TIMER_SEC_INT = "ims.registration_expiry_timer_sec_int";
     field public static final String KEY_REGISTRATION_RETRY_BASE_TIMER_MILLIS_INT = "ims.registration_retry_base_timer_millis_int";
@@ -42243,7 +42567,6 @@
     field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
     field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
     field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
-    field public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.enable_support_for_eap_aka_fast_reauth_bool";
     field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array";
     field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int";
     field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int";
@@ -42265,6 +42588,7 @@
     field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array";
     field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array";
     field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array";
+    field public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.supports_eap_aka_fast_reauth_bool";
   }
 
   public abstract class CellIdentity implements android.os.Parcelable {
@@ -44650,6 +44974,7 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    method @NonNull public android.telephony.ims.ProvisioningManager getProvisioningManager(int);
     field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR";
     field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE";
     field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE";
@@ -44900,6 +45225,23 @@
     field public static final int REASON_UNKNOWN_TEMPORARY_ERROR = 1; // 0x1
   }
 
+  public class ProvisioningManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isProvisioningRequiredForCapability(int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isRcsProvisioningRequiredForCapability(int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerFeatureProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, int, boolean);
+    method public void unregisterFeatureProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback);
+  }
+
+  public static class ProvisioningManager.FeatureProvisioningCallback {
+    ctor public ProvisioningManager.FeatureProvisioningCallback();
+    method public void onFeatureProvisioningChanged(int, int, boolean);
+    method public void onRcsFeatureProvisioningChanged(int, int, boolean);
+  }
+
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
   }
@@ -44940,6 +45282,27 @@
     field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1
   }
 
+  public class RcsFeature {
+  }
+
+  public static class RcsFeature.RcsImsCapabilities {
+    field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+    field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+    field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
+  }
+
+}
+
+package android.telephony.ims.stub {
+
+  public class ImsRegistrationImplBase {
+    field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2
+    field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1
+    field public static final int REGISTRATION_TECH_LTE = 0; // 0x0
+    field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff
+    field public static final int REGISTRATION_TECH_NR = 3; // 0x3
+  }
+
 }
 
 package android.telephony.mbms {
@@ -48155,6 +48518,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 {
@@ -48531,6 +48895,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);
@@ -49477,6 +49842,22 @@
     field public int toolType;
   }
 
+  public interface OnBackInvokedCallback {
+    method public default void onBackInvoked();
+  }
+
+  public abstract class OnBackInvokedDispatcher {
+    ctor public OnBackInvokedDispatcher();
+    method public abstract void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, int);
+    method public abstract void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback);
+    field public static final int PRIORITY_DEFAULT = 0; // 0x0
+    field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
+  }
+
+  public interface OnBackInvokedDispatcherOwner {
+    method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
+  }
+
   public interface OnReceiveContentListener {
     method @Nullable public android.view.ContentInfo onReceiveContent(@NonNull android.view.View, @NonNull android.view.ContentInfo);
   }
@@ -49900,7 +50281,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.VerifiedMotionEvent> CREATOR;
   }
 
-  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
     ctor public View(android.content.Context);
     ctor public View(android.content.Context, @Nullable android.util.AttributeSet);
     ctor public View(android.content.Context, @Nullable android.util.AttributeSet, int);
@@ -50104,6 +50485,7 @@
     method @IdRes public int getNextFocusLeftId();
     method @IdRes public int getNextFocusRightId();
     method @IdRes public int getNextFocusUpId();
+    method @Nullable public android.view.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
     method @ColorInt public int getOutlineAmbientShadowColor();
     method public android.view.ViewOutlineProvider getOutlineProvider();
@@ -50121,6 +50503,7 @@
     method public float getPivotX();
     method public float getPivotY();
     method public android.view.PointerIcon getPointerIcon();
+    method @NonNull public final java.util.List<android.graphics.Rect> getPreferKeepClearRects();
     method @Nullable public String[] getReceiveContentMimeTypes();
     method public android.content.res.Resources getResources();
     method public final boolean getRevealOnFocusHint();
@@ -50238,6 +50621,7 @@
     method protected boolean isPaddingOffsetRequired();
     method public boolean isPaddingRelative();
     method public boolean isPivotSet();
+    method public final boolean isPreferKeepClear();
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
@@ -50480,6 +50864,8 @@
     method public void setPivotX(float);
     method public void setPivotY(float);
     method public void setPointerIcon(android.view.PointerIcon);
+    method public final void setPreferKeepClear(boolean);
+    method public final void setPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>);
     method public void setPressed(boolean);
     method public void setRenderEffect(@Nullable android.graphics.RenderEffect);
     method public final void setRevealOnFocusHint(boolean);
@@ -54877,6 +55263,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();
@@ -54898,7 +55285,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();
@@ -54921,6 +55308,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);
@@ -54942,7 +55330,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);
@@ -54973,9 +55361,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
@@ -57140,8 +57528,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 de8fe8f..ee31e13 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -9,7 +9,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
   }
 
@@ -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";
   }
 
@@ -73,12 +75,36 @@
   public class NetworkStatsManager {
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void forceUpdate();
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void notifyNetworkStatus(@NonNull java.util.List<android.net.Network>, @NonNull java.util.List<android.net.NetworkStateSnapshot>, @Nullable String, @NonNull java.util.List<android.net.UnderlyingNetworkInfo>);
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForDevice(@NonNull android.net.NetworkTemplate, long, long);
+    method @NonNull @WorkerThread public android.app.usage.NetworkStats queryDetailsForUidTagState(@NonNull android.net.NetworkTemplate, long, long, int, int, int) throws java.lang.SecurityException;
+    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);
+  }
+
 }
 
 package android.content {
@@ -134,10 +160,12 @@
     field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2
     field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff
     field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff
+    field public static final int USB_HAL_RETRY = -2; // 0xfffffffe
     field public static final int USB_HAL_V1_0 = 10; // 0xa
     field public static final int USB_HAL_V1_1 = 11; // 0xb
     field public static final int USB_HAL_V1_2 = 12; // 0xc
     field public static final int USB_HAL_V1_3 = 13; // 0xd
+    field public static final int USB_HAL_V2_0 = 20; // 0x14
   }
 
 }
@@ -251,6 +279,36 @@
     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();
+    method @Nullable public String getSubscriberId();
+    method public int getType();
+    method @Nullable public String getWifiNetworkKey();
+    method public boolean isDefaultNetwork();
+    method public boolean isMetered();
+    method public boolean isRoaming();
+  }
+
+  public static final class NetworkIdentity.Builder {
+    ctor public NetworkIdentity.Builder();
+    method @NonNull public android.net.NetworkIdentity build();
+    method @NonNull public android.net.NetworkIdentity.Builder clearRatType();
+    method @NonNull public android.net.NetworkIdentity.Builder setDefaultNetwork(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setMetered(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setNetworkStateSnapshot(@NonNull android.net.NetworkStateSnapshot);
+    method @NonNull public android.net.NetworkIdentity.Builder setOemManaged(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setRatType(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setRoaming(boolean);
+    method @NonNull public android.net.NetworkIdentity.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkIdentity.Builder setType(int);
+    method @NonNull public android.net.NetworkIdentity.Builder setWifiNetworkKey(@Nullable String);
+  }
+
   public class NetworkPolicyManager {
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int);
@@ -278,6 +336,44 @@
     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();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStatsHistory> CREATOR;
+  }
+
+  public static final class NetworkStatsHistory.Builder {
+    ctor public NetworkStatsHistory.Builder(long, int);
+    method @NonNull public android.net.NetworkStatsHistory.Builder addEntry(@NonNull android.net.NetworkStatsHistory.Entry);
+    method @NonNull public android.net.NetworkStatsHistory build();
+  }
+
+  public static final class NetworkStatsHistory.Entry {
+    ctor public NetworkStatsHistory.Entry(long, long, long, long, long, long, long);
+    method public long getActiveTime();
+    method public long getBucketStart();
+    method public long getOperations();
+    method public long getRxBytes();
+    method public long getRxPackets();
+    method public long getTxBytes();
+    method public long getTxPackets();
+  }
+
   public final class NetworkTemplate implements android.os.Parcelable {
     method public int describeContents();
     method public int getDefaultNetworkStatus();
@@ -333,6 +429,11 @@
     method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
   }
 
+  public class TrafficStats {
+    method public static void attachSocketTagger();
+    method public static void init(@NonNull android.content.Context);
+  }
+
   public final class UnderlyingNetworkInfo implements android.os.Parcelable {
     ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public int describeContents();
@@ -363,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();
   }
@@ -376,6 +492,10 @@
   }
 
   public class Process {
+    method public static final boolean isSupplemental(int);
+    method public static final int toAppUid(int);
+    method public static final int toSupplementalUid(int);
+    field public static final int NFC_UID = 1027; // 0x403
     field public static final int VPN_UID = 1016; // 0x3f8
   }
 
@@ -407,6 +527,16 @@
     method @NonNull public java.util.List<android.content.ComponentName> getEnabledComponentOverrides(@NonNull String);
   }
 
+  public final class Trace {
+    method public static void asyncTraceBegin(long, @NonNull String, int);
+    method public static void asyncTraceEnd(long, @NonNull String, int);
+    method public static boolean isTagEnabled(long);
+    method public static void traceBegin(long, @NonNull String);
+    method public static void traceCounter(long, @NonNull String, int);
+    method public static void traceEnd(long);
+    field public static final long TRACE_TAG_NETWORK = 2097152L; // 0x200000L
+  }
+
 }
 
 package android.os.storage {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 07639fb..311b110 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -513,7 +513,7 @@
 
 package android.view {
 
-  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
     method protected void initializeFadingEdge(android.content.res.TypedArray);
     method protected void initializeScrollbars(android.content.res.TypedArray);
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2269750..ea2a641 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2,12 +2,14 @@
 package android {
 
   public static final class Manifest.permission {
+    field public static final String ACCESS_AMBIENT_CONTEXT_EVENT = "android.permission.ACCESS_AMBIENT_CONTEXT_EVENT";
     field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
     field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO";
     field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM";
     field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
+    field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
     field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
     field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
@@ -23,6 +25,7 @@
     field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
     field public static final String ACCESS_TV_SHARED_FILTER = "android.permission.ACCESS_TV_SHARED_FILTER";
     field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
+    field public static final String ACCESS_ULTRASOUND = "android.permission.ACCESS_ULTRASOUND";
     field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
@@ -36,6 +39,7 @@
     field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
     field public static final String BACKUP = "android.permission.BACKUP";
     field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
+    field public static final String BIND_AMBIENT_CONTEXT_DETECTION_SERVICE = "android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE";
     field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE";
     field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE";
     field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE";
@@ -138,6 +142,7 @@
     field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
     field public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
     field public static final String KILL_UID = "android.permission.KILL_UID";
+    field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP";
     field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
     field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
     field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
@@ -157,6 +162,7 @@
     field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
+    field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
     field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION";
     field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
     field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
@@ -281,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";
@@ -296,6 +303,7 @@
     field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
     field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
+    field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
@@ -313,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";
@@ -364,6 +373,7 @@
   }
 
   public static final class R.bool {
+    field public static final int config_enableQrCodeScannerOnLockScreen;
     field public static final int config_sendPackageName = 17891328; // 0x1110000
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -391,6 +401,7 @@
     field public static final int config_customMediaKeyDispatcher = 17039404; // 0x104002c
     field public static final int config_customMediaSessionPolicyProvider = 17039405; // 0x104002d
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
+    field public static final int config_defaultAutomotiveNavigation;
     field public static final int config_defaultBrowser = 17039394; // 0x1040022
     field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
@@ -443,7 +454,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public void convertFromTranslucent();
     method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions);
     method @Deprecated public boolean isBackgroundVisibleBehind();
@@ -750,7 +761,17 @@
   }
 
   public final class GameManager {
-    method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int);
+  }
+
+  public final class GameModeInfo implements android.os.Parcelable {
+    ctor public GameModeInfo(int, @NonNull int[]);
+    method public int describeContents();
+    method public int getActiveGameMode();
+    method @NonNull public int[] getAvailableGameModes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR;
   }
 
   public abstract class InstantAppResolverService extends android.app.Service {
@@ -991,8 +1012,10 @@
 
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
+    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
   }
 
 }
@@ -1000,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;
   }
@@ -1032,6 +1055,8 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
+    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>);
+    method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
@@ -1043,26 +1068,31 @@
     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>);
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
     method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setStrings(@NonNull java.util.Set<android.app.admin.DevicePolicyStringResource>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     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";
     field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
-    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
-    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
-    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
-    field @RequiresPermission("android.permission.LAUNCH_DEVICE_MANAGER_SETUP") public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
     field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
     field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
     field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -1079,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";
@@ -1116,6 +1147,48 @@
     field public static final int STATE_USER_UNMANAGED = 0; // 0x0
   }
 
+  public static final class DevicePolicyResources.Strings {
+    field public static final String UNDEFINED = "UNDEFINED";
+  }
+
+  public static final class DevicePolicyResources.Strings.DocumentsUi {
+    field public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE";
+    field public static final String CANT_SAVE_TO_PERSONAL_TITLE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE";
+    field public static final String CANT_SAVE_TO_WORK_MESSAGE = "DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE";
+    field public static final String CANT_SAVE_TO_WORK_TITLE = "DocumentsUi.CANT_SAVE_TO_WORK_TITLE";
+    field public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE";
+    field public static final String CANT_SELECT_PERSONAL_FILES_TITLE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE";
+    field public static final String CANT_SELECT_WORK_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE";
+    field public static final String CANT_SELECT_WORK_FILES_TITLE = "DocumentsUi.CANT_SELECT_WORK_FILES_TITLE";
+    field public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE";
+    field public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE";
+    field public static final String PERSONAL_TAB = "DocumentsUi.PERSONAL_TAB";
+    field public static final String PREVIEW_WORK_FILE_ACCESSIBILITY = "DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY";
+    field public static final String WORK_ACCESSIBILITY = "DocumentsUi.WORK_ACCESSIBILITY";
+    field public static final String WORK_PROFILE_OFF_ENABLE_BUTTON = "DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON";
+    field public static final String WORK_PROFILE_OFF_ERROR_TITLE = "DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE";
+    field public static final String WORK_TAB = "DocumentsUi.WORK_TAB";
+  }
+
+  public static final class DevicePolicyResources.Strings.MediaProvider {
+    field public static final String BLOCKED_BY_ADMIN_TITLE = "MediaProvider.BLOCKED_BY_ADMIN_TITLE";
+    field public static final String BLOCKED_FROM_PERSONAL_MESSAGE = "MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE";
+    field public static final String BLOCKED_FROM_WORK_MESSAGE = "MediaProvider.BLOCKED_FROM_WORK_MESSAGE";
+    field public static final String SWITCH_TO_PERSONAL_MESSAGE = "MediaProvider.SWITCH_TO_PERSONAL_MESSAGE";
+    field public static final String SWITCH_TO_WORK_MESSAGE = "MediaProvider.SWITCH_TO_WORK_MESSAGE";
+    field public static final String WORK_PROFILE_PAUSED_MESSAGE = "MediaProvider.WORK_PROFILE_PAUSED_MESSAGE";
+    field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE";
+  }
+
+  public final class DevicePolicyStringResource implements android.os.Parcelable {
+    ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int);
+    method public int describeContents();
+    method public int getCallingPackageResourceId();
+    method @NonNull public String getStringId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyStringResource> CREATOR;
+  }
+
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
     method public boolean canDeviceOwnerGrantSensorsPermissions();
     method public int describeContents();
@@ -1187,6 +1260,87 @@
 
 }
 
+package android.app.ambientcontext {
+
+  public final class AmbientContextEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConfidenceLevel();
+    method public int getDensityLevel();
+    method @NonNull public java.time.Instant getEndTime();
+    method public int getEventType();
+    method @NonNull public java.time.Instant getStartTime();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
+    field public static final int EVENT_COUGH = 1; // 0x1
+    field public static final int EVENT_SNORE = 2; // 0x2
+    field public static final int EVENT_UNKNOWN = 0; // 0x0
+    field public static final int LEVEL_HIGH = 5; // 0x5
+    field public static final int LEVEL_LOW = 1; // 0x1
+    field public static final int LEVEL_MEDIUM = 3; // 0x3
+    field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
+    field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
+    field public static final int LEVEL_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class AmbientContextEvent.Builder {
+    ctor public AmbientContextEvent.Builder();
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent build();
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setConfidenceLevel(int);
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int);
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
+  }
+
+  public final class AmbientContextEventRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+    method @NonNull public android.os.PersistableBundle getOptions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventRequest> CREATOR;
+  }
+
+  public static final class AmbientContextEventRequest.Builder {
+    ctor public AmbientContextEventRequest.Builder();
+    method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder addEventType(int);
+    method @NonNull public android.app.ambientcontext.AmbientContextEventRequest build();
+    method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle);
+  }
+
+  public final class AmbientContextEventResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.app.PendingIntent getActionPendingIntent();
+    method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents();
+    method @NonNull public String getPackageName();
+    method public int getStatusCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR;
+    field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+    field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4
+    field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2
+    field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class AmbientContextEventResponse.Builder {
+    ctor public AmbientContextEventResponse.Builder();
+    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent);
+    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build();
+    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent);
+    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String);
+    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int);
+  }
+
+  public final class AmbientContextManager {
+    method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver();
+    field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
+  }
+
+}
+
 package android.app.assist {
 
   public class ActivityId {
@@ -2036,6 +2190,8 @@
   }
 
   public class NetworkStatsManager {
+    method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getMobileUidStats();
+    method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public android.net.NetworkStats getWifiUidStats();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.NetworkStatsProvider);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STATS_PROVIDER, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void unregisterNetworkStatsProvider(@NonNull android.net.netstats.provider.NetworkStatsProvider);
   }
@@ -2212,6 +2368,7 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isEncrypted();
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isInSilenceMode();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond();
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setLowLatencyAudioAllowed(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setMessageAccessPermission(int);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setMetadata(int, @NonNull byte[]);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setPhonebookAccessPermission(int);
@@ -2221,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
@@ -2264,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);
@@ -2282,6 +2450,10 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioLocation(@NonNull android.bluetooth.BluetoothDevice);
+  }
+
   public final class BluetoothMap implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
     method public void close();
     method protected void finalize();
@@ -2291,15 +2463,21 @@
     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 {
     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 getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn();
-    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean);
+    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
     field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
@@ -2320,6 +2498,15 @@
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothPbapClient 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.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public interface BluetoothProfile {
     field public static final int A2DP_SINK = 11; // 0xb
     field public static final int AVRCP_CONTROLLER = 12; // 0xc
@@ -2361,6 +2548,7 @@
     field @NonNull public static final android.os.ParcelUuid COORDINATED_SET;
     field @NonNull public static final android.os.ParcelUuid DIP;
     field @NonNull public static final android.os.ParcelUuid GENERIC_MEDIA_CONTROL;
+    field @NonNull public static final android.os.ParcelUuid HAS;
     field @NonNull public static final android.os.ParcelUuid HEARING_AID;
     field @NonNull public static final android.os.ParcelUuid HFP;
     field @NonNull public static final android.os.ParcelUuid HFP_AG;
@@ -2577,10 +2765,32 @@
 package android.companion.virtual {
 
   public final class VirtualDeviceManager {
+    method @Nullable @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
   }
 
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void close();
+    method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+  }
+
+  public final class VirtualDeviceParams implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLockState();
+    method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
+    field public static final int LOCK_STATE_ALWAYS_LOCKED = 0; // 0x0
+    field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
+  }
+
+  public static final class VirtualDeviceParams.Builder {
+    ctor public VirtualDeviceParams.Builder();
+    method @NonNull public android.companion.virtual.VirtualDeviceParams build();
+    method @NonNull @RequiresPermission(value="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY", conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
   }
 
 }
@@ -2638,6 +2848,7 @@
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+    field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
     field public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String APP_PREDICTION_SERVICE = "app_prediction";
@@ -3634,6 +3845,7 @@
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
+    field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
 
 }
@@ -4069,6 +4281,7 @@
 
   public class VirtualMouse implements java.io.Closeable {
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition();
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent);
@@ -5028,8 +5241,27 @@
   }
 
   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
+    field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4
+    field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3; // 0x3
+    field public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0; // 0x0
+    field public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1; // 0x1
+    field public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2; // 0x2
+    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 {
@@ -5037,8 +5269,11 @@
     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);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR;
@@ -5050,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
   }
 
 }
@@ -5669,6 +5914,7 @@
     method public int getCapturePreset();
     method public int getSystemUsage();
     method public static boolean isSystemUsage(int);
+    field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int CONTENT_TYPE_ULTRASOUND = 1997; // 0x7cd
     field public static final int FLAG_BEACON = 8; // 0x8
     field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40
     field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
@@ -5685,6 +5931,7 @@
     method public android.media.AudioAttributes.Builder setCapturePreset(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordModeEnabled(boolean);
     method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
+    method @NonNull public android.media.AudioAttributes.Builder setInternalContentType(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
   }
 
@@ -5900,6 +6147,7 @@
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
+    field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int ULTRASOUND = 2000; // 0x7d0
   }
 
   public final class MediaRouter2 {
@@ -6554,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);
@@ -6565,6 +6814,7 @@
     method @Nullable public android.media.tv.tuner.Lnb openLnbByName(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER) public static android.media.tv.tuner.filter.SharedFilter openSharedFilter(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.filter.SharedFilterCallback);
     method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
+    method public int removeOutputPid(@IntRange(from=0) int);
     method public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
     method public int setLnaEnabled(boolean);
     method public int setMaxNumberOfFrontends(int, @IntRange(from=0) int);
@@ -7005,7 +7255,7 @@
   }
 
   public abstract class SectionSettings extends android.media.tv.tuner.filter.Settings {
-    method public int getBitWidthOfLengthField();
+    method public int getLengthFieldBitWidth();
     method public boolean isCrcEnabled();
     method public boolean isRaw();
     method public boolean isRepeat();
@@ -7705,6 +7955,7 @@
 
   public class FrontendStatus {
     method public int getAgc();
+    method @NonNull public android.media.tv.tuner.frontend.Atsc3PlpInfo[] getAllAtsc3PlpInfo();
     method @NonNull public android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo[] getAtsc3PlpTuningInfo();
     method public int getBandwidth();
     method public int getBer();
@@ -7747,6 +7998,7 @@
     method public boolean isRfLocked();
     method public boolean isShortFramesEnabled();
     field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe
+    field public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO = 41; // 0x29
     field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15
     field public static final int FRONTEND_STATUS_TYPE_BANDWIDTH = 25; // 0x19
     field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2
@@ -7795,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();
@@ -8155,11 +8417,12 @@
     field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
   }
 
-  public final class NetworkStats implements android.os.Parcelable {
+  public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable {
     ctor public NetworkStats(long, int);
     method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
     method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
     method public int describeContents();
+    method @NonNull public java.util.Iterator<android.net.NetworkStats.Entry> iterator();
     method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR;
@@ -8637,6 +8900,7 @@
     method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int);
     method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
+    method public boolean notifyCountryCodeChanged();
     method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
     method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener);
@@ -9634,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
   }
@@ -9657,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);
@@ -9665,9 +9935,9 @@
     method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
     method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
+    method @BinderThread public void onRevokeOwnPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
-    method @BinderThread public void onSelfRevokePermissions(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable);
     method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
@@ -9681,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();
@@ -9875,6 +10147,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
+    field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
     field public static final String NAMESPACE_APPSEARCH = "appsearch";
     field public static final String NAMESPACE_APP_COMPAT = "app_compat";
     field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
@@ -9917,6 +10190,7 @@
     field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
     field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
     field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
+    field public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api";
     field public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot";
     field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
@@ -10145,6 +10419,7 @@
     field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled";
     field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
     field public static final String DOZE_ALWAYS_ON = "doze_always_on";
+    field public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
     field public static final String HUSH_GESTURE_USED = "hush_gesture_used";
     field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled";
     field public static final String LAST_SETUP_SHOWN = "last_setup_shown";
@@ -10445,6 +10720,18 @@
 
 }
 
+package android.service.ambientcontext {
+
+  public abstract class AmbientContextDetectionService extends android.app.Service {
+    ctor public AmbientContextDetectionService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>);
+    method public abstract void onStopDetection(@NonNull String);
+    field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService";
+  }
+
+}
+
 package android.service.appprediction {
 
   public abstract class AppPredictionService extends android.app.Service {
@@ -10896,6 +11183,15 @@
     ctor public GameSession();
     method public void onCreate();
     method public void onDestroy();
+    method public void onGameTaskFocusChanged(boolean);
+    method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams);
+    method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback);
+  }
+
+  public static interface GameSession.ScreenshotCallback {
+    method public void onFailure(int);
+    method public void onSuccess(@NonNull android.graphics.Bitmap);
+    field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0
   }
 
   public abstract class GameSessionService extends android.app.Service {
@@ -11036,6 +11332,7 @@
     method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
     method public long getMaximumDataBlockSize();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
+    method @NonNull public String getPersistentDataPackageName();
     method public byte[] read();
     method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
     method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
@@ -12993,6 +13290,7 @@
     field public static final int ALLOWED_NETWORK_TYPES_REASON_USER = 0; // 0x0
     field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
     field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
+    field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
     field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
     field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
@@ -14318,18 +14616,16 @@
   public class ProvisioningManager {
     method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isRcsVolteSingleRegistrationCapable() throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerRcsProvisioningCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration) throws android.telephony.ims.ImsException;
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
     method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerRcsReconfiguration();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void unregisterRcsProvisioningCallback(@NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback);
@@ -14486,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
@@ -14762,9 +15059,6 @@
     method public void addCapabilities(int);
     method public boolean isCapable(int);
     method public void removeCapabilities(int);
-    field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
-    field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
-    field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
   }
 
 }
@@ -14914,11 +15208,6 @@
     method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String);
     method public void triggerSipDelegateDeregistration();
     method public void updateSipDelegateRegistration();
-    field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2
-    field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1
-    field public static final int REGISTRATION_TECH_LTE = 0; // 0x0
-    field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff
-    field public static final int REGISTRATION_TECH_NR = 3; // 0x3
   }
 
   public class ImsSmsImplBase {
@@ -15196,6 +15485,8 @@
 
   public interface WindowManager extends android.view.ViewManager {
     method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public android.graphics.Region getCurrentImeTouchRegion();
+    method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull android.window.TaskFpsCallback);
+    method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback);
   }
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
@@ -15460,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 {
@@ -15783,3 +16075,15 @@
 
 }
 
+package android.window {
+
+  public final class TaskFpsCallback {
+    ctor public TaskFpsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback.OnFpsCallbackListener);
+  }
+
+  public static interface TaskFpsCallback.OnFpsCallbackListener {
+    method public void onFpsReported(float);
+  }
+
+}
+
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 716f43e..3d756ba 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -91,6 +91,10 @@
 
 
 
+NoSettingsProvider: android.provider.Settings.Secure#FAST_PAIR_SCAN_ENABLED:
+    New setting keys are not allowed (Field: FAST_PAIR_SCAN_ENABLED); use getters/setters in relevant manager class
+
+
 OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
     Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
 
@@ -103,6 +107,16 @@
 
 
 
+RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimAmount():
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.WallpaperManager#getWallpaperDimmingAmount():
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimAmount(float):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+RethrowRemoteException: android.app.WallpaperManager#setWallpaperDimmingAmount(float):
+    Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
+
+
 SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
 
 SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d6a9f7f..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";
@@ -101,7 +102,7 @@
 
 package android.app {
 
-  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
+  @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public void onMovedToDisplay(int, android.content.res.Configuration);
   }
 
@@ -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
   }
@@ -1928,6 +1943,9 @@
     method @NonNull public static String convert(@NonNull java.util.UUID);
     method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
     method public static boolean isUserKeyUnlocked(int);
+    field public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high";
+    field public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low";
+    field public static final String STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high";
   }
 
   public final class StorageVolume implements android.os.Parcelable {
@@ -2737,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 {
@@ -2779,7 +2798,7 @@
     method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams);
   }
 
-  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+  @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback android.view.OnBackInvokedDispatcherOwner {
     method public android.view.View getTooltipView();
     method public boolean isAutofilled();
     method public static boolean isDefaultFocusHighlightEnabled();
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/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 3c9b232..8e01779 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -172,7 +172,7 @@
     private AccessibilityGestureEvent(@NonNull Parcel parcel) {
         mGestureId = parcel.readInt();
         mDisplayId = parcel.readInt();
-        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader());
+        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class);
         mMotionEvents = slice.getList();
     }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 04c784e..1167d0b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -1094,8 +1094,8 @@
         mInteractiveUiTimeout = parcel.readInt();
         flags = parcel.readInt();
         crashed = parcel.readInt() != 0;
-        mComponentName = parcel.readParcelable(this.getClass().getClassLoader());
-        mResolveInfo = parcel.readParcelable(null);
+        mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class);
+        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
         mSettingsActivityName = parcel.readString();
         mCapabilities = parcel.readInt();
         mSummaryResId = parcel.readInt();
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index a8ba1d3..bb2b8d4 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -24,6 +24,8 @@
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 
+import java.util.LinkedList;
+import java.util.Queue;
 import java.util.concurrent.Executor;
 
 /**
@@ -102,6 +104,11 @@
     private boolean mServiceDetectsGestures;
     /** Map of callbacks to executors. Lazily created when adding the first callback. */
     private ArrayMap<Callback, Executor> mCallbacks;
+    // A list of motion events that should be queued until a pending transition has taken place.
+    private Queue<MotionEvent> mQueuedMotionEvents = new LinkedList<>();
+    // Whether this controller is waiting for a state transition.
+    // Motion events will be queued and sent to listeners after the transition has taken place.
+    private boolean mStateChangeRequested = false;
 
     // The current state of the display.
     private int mState = STATE_CLEAR;
@@ -169,6 +176,14 @@
      * main thread.
      */
     void onMotionEvent(MotionEvent event) {
+        if (mStateChangeRequested) {
+            mQueuedMotionEvents.add(event);
+        } else {
+            sendEventToAllListeners(event);
+        }
+    }
+
+    private void sendEventToAllListeners(MotionEvent event) {
         final ArrayMap<Callback, Executor> entries;
         synchronized (mLock) {
             // callbacks may remove themselves. Perform a shallow copy to avoid concurrent
@@ -209,6 +224,10 @@
                 callback.onStateChanged(state);
             }
         }
+        mStateChangeRequested = false;
+        while (mQueuedMotionEvents.size() > 0) {
+            sendEventToAllListeners(mQueuedMotionEvents.poll());
+        }
     }
 
     /**
@@ -253,6 +272,7 @@
             } catch (RemoteException re) {
                 throw new RuntimeException(re);
             }
+            mStateChangeRequested = true;
         }
     }
 
@@ -281,6 +301,7 @@
             } catch (RemoteException re) {
                 throw new RuntimeException(re);
             }
+            mStateChangeRequested = true;
         }
     }
 
@@ -302,6 +323,7 @@
             } catch (RemoteException re) {
                 throw new RuntimeException(re);
             }
+            mStateChangeRequested = true;
         }
     }
 
diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java
index f7f232e..107efc3 100644
--- a/core/java/android/accounts/CantAddAccountActivity.java
+++ b/core/java/android/accounts/CantAddAccountActivity.java
@@ -16,9 +16,13 @@
 package android.accounts;
 
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
 import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
 import android.os.Bundle;
 import android.view.View;
+import android.widget.TextView;
 
 import com.android.internal.R;
 
@@ -33,6 +37,12 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.app_not_authorized);
+
+        TextView view = findViewById(R.id.description);
+        String text = getSystemService(DevicePolicyManager.class).getString(
+                CANT_ADD_ACCOUNT_MESSAGE,
+                () -> getString(R.string.error_message_change_not_allowed));
+        view.setText(text);
     }
 
     public void onCancelButtonClicked(View view) {
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 2e9f73c..0d82ac9 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -15,7 +15,10 @@
  */
 package android.accounts;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+
 import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -199,7 +202,14 @@
 
         if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
             requestWindowFeature(Window.FEATURE_NO_TITLE);
+
             setContentView(R.layout.app_not_authorized);
+            TextView view = findViewById(R.id.description);
+            String text = getSystemService(DevicePolicyManager.class).getString(
+                    CANT_ADD_ACCOUNT_MESSAGE,
+                    () -> getString(R.string.error_message_change_not_allowed));
+            view.setText(text);
+
             mDontShowPicker = true;
         }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 283345f..a7b96a6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -111,6 +111,8 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.OnBackInvokedDispatcher;
+import android.view.OnBackInvokedDispatcherOwner;
 import android.view.RemoteAnimationDefinition;
 import android.view.SearchEvent;
 import android.view.View;
@@ -736,7 +738,8 @@
         Window.Callback, KeyEvent.Callback,
         OnCreateContextMenuListener, ComponentCallbacks2,
         Window.OnWindowDismissedCallback,
-        ContentCaptureManager.ContentCaptureClient {
+        ContentCaptureManager.ContentCaptureClient,
+        OnBackInvokedDispatcherOwner {
     private static final String TAG = "Activity";
     private static final boolean DEBUG_LIFECYCLE = false;
 
@@ -1521,7 +1524,10 @@
     }
 
     private void dispatchActivityConfigurationChanged() {
-        getApplication().dispatchActivityConfigurationChanged(this);
+        // In case the new config comes before mApplication is assigned.
+        if (getApplication() != null) {
+            getApplication().dispatchActivityConfigurationChanged(this);
+        }
         Object[] callbacks = collectActivityLifecycleCallbacks();
         if (callbacks != null) {
             for (int i = 0; i < callbacks.length; i++) {
@@ -8672,4 +8678,22 @@
             return (w != null && w.peekDecorView() != null);
         }
     }
+
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this
+     * activity is attached to.
+     *
+     * Returns null if the activity is not attached to a window with a decor.
+     */
+    @Nullable
+    @Override
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        if (mWindow != null) {
+            View decorView = mWindow.getDecorView();
+            if (decorView != null) {
+                return decorView.getOnBackInvokedDispatcher();
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9f8d246..a140983 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1903,7 +1903,7 @@
         public void readFromParcel(Parcel source) {
             id = source.readInt();
             persistentId = source.readInt();
-            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
+            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class);
             lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
             lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
             lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 686ca3b..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;
@@ -105,9 +106,11 @@
 import android.media.MediaServiceManager;
 import android.net.ConnectivityManager;
 import android.net.Proxy;
+import android.net.TrafficStats;
 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;
@@ -6725,6 +6728,13 @@
         NetworkSecurityConfigProvider.install(appContext);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
+        // For backward compatibility, TrafficStats needs static access to the application context.
+        // But for isolated apps which cannot access network related services, service discovery
+        // is restricted. Hence, calling this would result in NPE.
+        if (!Process.isIsolated()) {
+            TrafficStats.init(appContext);
+        }
+
         // Continue loading instrumentation.
         if (ii != null) {
             initInstrumentation(ii, data, appContext);
@@ -7911,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 565f690..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
@@ -4058,7 +4058,7 @@
                 LongSparseArray<NoteOpEvent> array = new LongSparseArray<>(numEntries);
 
                 for (int i = 0; i < numEntries; i++) {
-                    array.put(source.readLong(), source.readParcelable(null));
+                    array.put(source.readLong(), source.readParcelable(null, android.app.AppOpsManager.NoteOpEvent.class));
                 }
 
                 return array;
@@ -5178,7 +5178,7 @@
             final int[] uids = parcel.createIntArray();
             if (!ArrayUtils.isEmpty(uids)) {
                 final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
-                        HistoricalOps.class.getClassLoader());
+                        HistoricalOps.class.getClassLoader(), android.content.pm.ParceledListSlice.class);
                 final List<HistoricalUidOps> uidOps = (listSlice != null)
                         ? listSlice.getList() : null;
                 if (uidOps == null) {
@@ -10000,7 +10000,7 @@
 
     private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel(
             @NonNull Parcel parcel) {
-        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null);
+        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
         return listSlice == null ? null : listSlice.getList();
     }
 
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/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 7a806bd..c0aebee 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -118,11 +118,11 @@
             name = source.readString();
         }
         interruptionFilter = source.readInt();
-        conditionId = source.readParcelable(null);
-        owner = source.readParcelable(null);
-        configurationActivity = source.readParcelable(null);
+        conditionId = source.readParcelable(null, android.net.Uri.class);
+        owner = source.readParcelable(null, android.content.ComponentName.class);
+        configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
         creationTime = source.readLong();
-        mZenPolicy = source.readParcelable(null);
+        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
         mModified = source.readInt() == ENABLED;
         mPkg = source.readString();
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fa48730..f3315a8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2179,8 +2179,8 @@
     }
 
     @Override
-    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
-        getSystemService(PermissionManager.class).selfRevokePermissions(permissions);
+    public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) {
+        getSystemService(PermissionManager.class).revokeOwnPermissionsOnKill(permissions);
     }
 
     @Override
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 3060353..a7fb83b 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -52,6 +52,8 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.OnBackInvokedDispatcher;
+import android.view.OnBackInvokedDispatcherOwner;
 import android.view.SearchEvent;
 import android.view.View;
 import android.view.View.OnCreateContextMenuListener;
@@ -94,7 +96,8 @@
  * </div>
  */
 public class Dialog implements DialogInterface, Window.Callback,
-        KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
+        KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback,
+        OnBackInvokedDispatcherOwner {
     private static final String TAG = "Dialog";
     @UnsupportedAppUsage
     private Activity mOwnerActivity;
@@ -1439,4 +1442,22 @@
             }
         }
     }
+
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this
+     * dialog is attached to.
+     *
+     * Returns null if the dialog is not attached to a window with a decor.
+     */
+    @Nullable
+    @Override
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        if (mWindow != null) {
+            View decorView = mWindow.getDecorView();
+            if (decorView != null) {
+                return decorView.getOnBackInvokedDispatcher();
+            }
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 29e1b70..76471d3 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -119,6 +119,31 @@
     }
 
     /**
+     * Returns the {@link GameModeInfo} associated with the game associated with
+     * the given {@code packageName}. If the given package is not a game, {@code null} is
+     * always returned.
+     * <p>
+     * An application can use <code>android:isGame="true"</code> or
+     * <code>android:appCategory="game"</code> to indicate that the application is a game.
+     * If the manifest doesn't define a category, the category can also be
+     * provided by the installer via
+     * {@link android.content.pm.PackageManager#setApplicationCategoryHint(String, int)}.
+     * <p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @UserHandleAware
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) {
+        try {
+            return mService.getGameModeInfo(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Sets the game mode for the given package.
      * <p>
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/app/GameModeInfo.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/app/GameModeInfo.aidl
index 2b3e961..3b13201 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/app/GameModeInfo.aidl
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.app;
 
-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 GameModeInfo;
\ No newline at end of file
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
new file mode 100644
index 0000000..fe0ac35
--- /dev/null
+++ b/core/java/android/app/GameModeInfo.java
@@ -0,0 +1,101 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}.
+ * @hide
+ */
+@SystemApi
+public final class GameModeInfo implements Parcelable {
+
+    public static final @NonNull Creator<GameModeInfo> CREATOR = new Creator<GameModeInfo>() {
+        @Override
+        public GameModeInfo createFromParcel(Parcel in) {
+            return new GameModeInfo(in);
+        }
+
+        @Override
+        public GameModeInfo[] newArray(int size) {
+            return new GameModeInfo[size];
+        }
+    };
+
+    public GameModeInfo(@GameManager.GameMode int activeGameMode,
+            @NonNull @GameManager.GameMode int[] availableGameModes) {
+        mActiveGameMode = activeGameMode;
+        mAvailableGameModes = availableGameModes;
+    }
+
+    GameModeInfo(Parcel in) {
+        mActiveGameMode = in.readInt();
+        final int availableGameModesCount = in.readInt();
+        mAvailableGameModes = new int[availableGameModesCount];
+        in.readIntArray(mAvailableGameModes);
+    }
+
+    /**
+     * Returns the {@link GameManager.GameMode} the application is currently using.
+     * Developers can enable game modes by adding
+     * <code>
+     *     <meta-data android:name="android.game_mode_intervention"
+     *             android:resource="@xml/GAME_MODE_CONFIG_FILE" />
+     * </code>
+     * to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that
+     * specifies the game mode enablement and configuration:
+     * <code>
+     *     <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android"
+     *         android:gameModePerformance="true"
+     *         android:gameModeBattery="false"
+     *     />
+     * </code>
+     */
+    public @GameManager.GameMode int getActiveGameMode() {
+        return mActiveGameMode;
+    }
+
+    /**
+     * The collection of {@link GameManager.GameMode GameModes} that can be applied to the game.
+     */
+    @NonNull
+    public @GameManager.GameMode int[] getAvailableGameModes() {
+        return mAvailableGameModes;
+    }
+
+    // Ideally there should be callback that the caller can register to know when the available
+    // GameMode and/or the active GameMode is changed, however, there's no concrete use case
+    // at the moment so there's no callback mechanism introduced    .
+    private final @GameManager.GameMode int[] mAvailableGameModes;
+    private final @GameManager.GameMode int mActiveGameMode;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mActiveGameMode);
+        dest.writeInt(mAvailableGameModes.length);
+        dest.writeIntArray(mAvailableGameModes);
+    }
+}
diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java
index 48d5b8c..a71cb4a 100644
--- a/core/java/android/app/GrantedUriPermission.java
+++ b/core/java/android/app/GrantedUriPermission.java
@@ -68,7 +68,7 @@
             };
 
     private GrantedUriPermission(Parcel in) {
-        uri = in.readParcelable(null);
+        uri = in.readParcelable(null, android.net.Uri.class);
         packageName = in.readString();
     }
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index a9ec11e..0801b24 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -72,6 +72,7 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.window.IWindowOrganizerController;
+import android.window.BackNavigationInfo;
 import android.window.SplashScreenView;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
@@ -346,7 +347,8 @@
     void setRunningRemoteTransitionDelegate(in IApplicationThread caller);
 
     /**
-     * Prepare the back preview in the server
+     * Prepare the back navigation in the server. This setups the leashed for sysui to animate
+     * the back gesture and returns the data needed for the animation.
      */
-    void startBackPreview(IRemoteAnimationRunner runner);
+    android.window.BackNavigationInfo startBackNavigation();
 }
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index d9aa586..57de8c7 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.app.GameModeInfo;
 import android.app.GameState;
 
 /**
@@ -27,4 +28,5 @@
     int[] getAvailableGameModes(String packageName);
     boolean getAngleEnabled(String packageName, int userId);
     void setGameState(String packageName, in GameState gameState, int userId);
-}
\ No newline at end of file
+    GameModeInfo getGameModeInfo(String packageName, int userId);
+}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 4f7c684..28c273e 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -204,4 +204,27 @@
      * @hide
      */
     void notifyGoingToSleep(int x, int y, in Bundle extras);
+
+    /**
+     * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
+     * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
+     *
+     * @hide
+     */
+    oneway void setWallpaperDimAmount(float dimAmount);
+
+    /**
+     * Gets the current additional dim amount set on the wallpaper. 0f means no application has
+     * added any dimming on top of the system default dim amount.
+     *
+     * @hide
+     */
+    float getWallpaperDimAmount();
+
+    /**
+     * Whether the lock screen wallpaper is different from the system wallpaper.
+     *
+     * @hide
+     */
+    boolean lockScreenWallpaperExists();
 }
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index cd6df0b..f97415c 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -100,7 +100,7 @@
         } else {
             mDescription = null;
         }
-        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
+        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java
index 091db3f..32f8981 100644
--- a/core/java/android/app/RemoteInputHistoryItem.java
+++ b/core/java/android/app/RemoteInputHistoryItem.java
@@ -48,7 +48,7 @@
     protected RemoteInputHistoryItem(Parcel in) {
         mText = in.readCharSequence();
         mMimeType = in.readStringNoHelper();
-        mUri = in.readParcelable(Uri.class.getClassLoader());
+        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final Creator<RemoteInputHistoryItem> CREATOR =
diff --git a/core/java/android/app/ServiceStartNotAllowedException.java b/core/java/android/app/ServiceStartNotAllowedException.java
index 33285b2..b1f47ee 100644
--- a/core/java/android/app/ServiceStartNotAllowedException.java
+++ b/core/java/android/app/ServiceStartNotAllowedException.java
@@ -40,4 +40,11 @@
             return new BackgroundServiceStartNotAllowedException(message);
         }
     }
+
+    @Override
+    public synchronized Throwable getCause() {
+        // "Cause" is often used for clustering exceptions, and developers don't want to have it
+        // for this exception. b/210890426
+        return null;
+    }
 }
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 67c42f6..7f8e46e 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -24,6 +24,8 @@
 import android.app.ContextImpl.ServiceInitializationState;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
+import android.app.ambientcontext.AmbientContextManager;
+import android.app.ambientcontext.IAmbientContextEventObserver;
 import android.app.appsearch.AppSearchManagerFrameworkInitializer;
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.contentsuggestions.ContentSuggestionsManager;
@@ -49,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;
@@ -124,8 +126,8 @@
 import android.media.soundtrigger.SoundTriggerManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
-import android.media.tv.interactive.ITvIAppManager;
-import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.ITvInteractiveAppManager;
+import android.media.tv.interactive.TvInteractiveAppManager;
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.nearby.NearbyFrameworkInitializer;
@@ -346,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
@@ -964,13 +959,16 @@
                     }
                 });
 
-        registerService(Context.TV_IAPP_SERVICE, TvIAppManager.class,
-                new CachedServiceFetcher<TvIAppManager>() {
+        registerService(Context.TV_INTERACTIVE_APP_SERVICE, TvInteractiveAppManager.class,
+                new CachedServiceFetcher<TvInteractiveAppManager>() {
             @Override
-            public TvIAppManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder iBinder = ServiceManager.getServiceOrThrow(Context.TV_IAPP_SERVICE);
-                ITvIAppManager service = ITvIAppManager.Stub.asInterface(iBinder);
-                return new TvIAppManager(service, ctx.getUserId());
+            public TvInteractiveAppManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                IBinder iBinder =
+                        ServiceManager.getServiceOrThrow(Context.TV_INTERACTIVE_APP_SERVICE);
+                ITvInteractiveAppManager service =
+                        ITvInteractiveAppManager.Stub.asInterface(iBinder);
+                return new TvInteractiveAppManager(service, ctx.getUserId());
             }});
 
         registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
@@ -1021,19 +1019,21 @@
             }});
 
         registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
-                new StaticServiceFetcher<PersistentDataBlockManager>() {
+                new CachedServiceFetcher<PersistentDataBlockManager>() {
             @Override
-            public PersistentDataBlockManager createService() throws ServiceNotFoundException {
+            public PersistentDataBlockManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
                 IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE);
                 IPersistentDataBlockService persistentDataBlockService =
                         IPersistentDataBlockService.Stub.asInterface(b);
                 if (persistentDataBlockService != null) {
-                    return new PersistentDataBlockManager(persistentDataBlockService);
+                    return new PersistentDataBlockManager(ctx, persistentDataBlockService);
                 } else {
                     // not supported
                     return null;
                 }
-            }});
+            }
+         });
 
         registerService(Context.OEM_LOCK_SERVICE, OemLockManager.class,
                 new StaticServiceFetcher<OemLockManager>() {
@@ -1518,6 +1518,18 @@
                     }
                 });
 
+        registerService(Context.AMBIENT_CONTEXT_SERVICE, AmbientContextManager.class,
+                new CachedServiceFetcher<AmbientContextManager>() {
+                    @Override
+                    public AmbientContextManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder = ServiceManager.getServiceOrThrow(
+                                Context.AMBIENT_CONTEXT_SERVICE);
+                        IAmbientContextEventObserver manager =
+                                IAmbientContextEventObserver.Stub.asInterface(iBinder);
+                        return new AmbientContextManager(ctx.getOuterContext(), manager);
+                    }});
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
@@ -1525,6 +1537,7 @@
             ConnectivityFrameworkInitializer.registerServiceWrappers();
             JobSchedulerFrameworkInitializer.registerServiceWrappers();
             BlobStoreManagerFrameworkInitializer.initialize();
+            BluetoothFrameworkInitializer.registerServiceWrappers();
             TelephonyFrameworkInitializer.registerServiceWrappers();
             AppSearchManagerFrameworkInitializer.initialize();
             WifiFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 7ef0a19..067a4c3 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -27,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Size;
 
 import com.android.internal.graphics.ColorUtils;
@@ -44,6 +46,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -173,6 +176,22 @@
         if (bitmap == null) {
             throw new IllegalArgumentException("Bitmap can't be null");
         }
+        return fromBitmap(bitmap, 0f /* dimAmount */);
+    }
+
+    /**
+     * Constructs {@link WallpaperColors} from a bitmap with dimming applied.
+     * <p>
+     * Main colors will be extracted from the bitmap with dimming taken into account when
+     * calculating dark hints.
+     *
+     * @param bitmap Source where to extract from.
+     * @param dimAmount Wallpaper dim amount
+     * @hide
+     */
+    public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
+            @FloatRange (from = 0f, to = 1f) float dimAmount) {
+        Objects.requireNonNull(bitmap, "Bitmap can't be null");
 
         final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
         boolean shouldRecycle = false;
@@ -211,7 +230,7 @@
 
         }
 
-        int hints = calculateDarkHints(bitmap);
+        int hints = calculateDarkHints(bitmap, dimAmount);
 
         if (shouldRecycle) {
             bitmap.recycle();
@@ -507,13 +526,15 @@
      * Checks if image is bright and clean enough to support light text.
      *
      * @param source What to read.
+     * @param dimAmount How much wallpaper dim amount was applied.
      * @return Whether image supports dark text or not.
      */
-    private static int calculateDarkHints(Bitmap source) {
+    private static int calculateDarkHints(Bitmap source, float dimAmount) {
         if (source == null) {
             return 0;
         }
 
+        dimAmount = MathUtils.saturate(dimAmount);
         int[] pixels = new int[source.getWidth() * source.getHeight()];
         double totalLuminance = 0;
         final int maxDarkPixels = (int) (pixels.length * MAX_DARK_AREA);
@@ -521,24 +542,37 @@
         source.getPixels(pixels, 0 /* offset */, source.getWidth(), 0 /* x */, 0 /* y */,
                 source.getWidth(), source.getHeight());
 
+        // Create a new black layer with dimAmount as the alpha to be accounted for when computing
+        // the luminance.
+        int dimmingLayerAlpha = (int) (255 * dimAmount);
+        int blackTransparent = ColorUtils.setAlphaComponent(Color.BLACK, dimmingLayerAlpha);
+
         // This bitmap was already resized to fit the maximum allowed area.
         // Let's just loop through the pixels, no sweat!
         float[] tmpHsl = new float[3];
         for (int i = 0; i < pixels.length; i++) {
-            ColorUtils.colorToHSL(pixels[i], tmpHsl);
-            final float luminance = tmpHsl[2];
-            final int alpha = Color.alpha(pixels[i]);
+            int pixelColor = pixels[i];
+            ColorUtils.colorToHSL(pixelColor, tmpHsl);
+            final int alpha = Color.alpha(pixelColor);
+
+            // Apply composite colors where the foreground is a black layer with an alpha value of
+            // the dim amount and the background is the wallpaper pixel color.
+            int compositeColors = ColorUtils.compositeColors(blackTransparent, pixelColor);
+
+            // Calculate the adjusted luminance of the dimmed wallpaper pixel color.
+            double adjustedLuminance = ColorUtils.calculateLuminance(compositeColors);
+
             // Make sure we don't have a dark pixel mass that will
             // make text illegible.
             final boolean satisfiesTextContrast = ContrastColorUtil
-                    .calculateContrast(pixels[i], Color.BLACK) > DARK_PIXEL_CONTRAST;
+                    .calculateContrast(pixelColor, Color.BLACK) > DARK_PIXEL_CONTRAST;
             if (!satisfiesTextContrast && alpha != 0) {
                 darkPixels++;
                 if (DEBUG_DARK_PIXELS) {
                     pixels[i] = Color.RED;
                 }
             }
-            totalLuminance += luminance;
+            totalLuminance += adjustedLuminance;
         }
 
         int hints = 0;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 61bf9b3..0a18588 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -71,6 +72,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Pair;
 import android.view.Display;
 import android.view.WindowManagerGlobal;
@@ -1994,6 +1996,63 @@
     }
 
     /**
+     * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
+     * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+    public void setWallpaperDimAmount(@FloatRange (from = 0f, to = 1f) float dimAmount) {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            sGlobals.mService.setWallpaperDimAmount(MathUtils.saturate(dimAmount));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the current additional dim amount set on the wallpaper. 0f means no application has
+     * added any dimming on top of the system default dim amount.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT)
+    public float getWallpaperDimAmount() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            return sGlobals.mService.getWallpaperDimAmount();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Whether the lock screen wallpaper is different from the system wallpaper.
+     *
+     * @hide
+     */
+    public boolean lockScreenWallpaperExists() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            throw new RuntimeException(new DeadSystemException());
+        }
+        try {
+            return sGlobals.mService.lockScreenWallpaperExists();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Set the live wallpaper.
      *
      * This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
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 9682593..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).
 /**
@@ -622,6 +623,32 @@
             "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
 
     /**
+     * A boolean extra indicating whether offline provisioning is allowed.
+     *
+     * <p>For the online provisioning flow, there will be an attempt to download and install
+     * the latest version of the device management role holder. The platform will then delegate
+     * provisioning to the device management role holder via role holder-specific provisioning
+     * actions.
+     *
+     * <p>For the offline provisioning flow, the provisioning flow will always be handled by
+     * the platform.
+     *
+     * <p>If this extra is set to {@code false}, the provisioning flow will enforce that an
+     * internet connection is established, which will start the online provisioning flow. If an
+     * internet connection cannot be established, provisioning will fail.
+     *
+     * <p>If this extra is set to {@code true}, the provisioning flow will still try to connect to
+     * the internet, but if it fails it will start the offline provisioning flow.
+     *
+     * <p>The default value is {@code false}.
+     *
+     * <p>This extra is respected when provided via the provisioning intent actions such as {@link
+     * #ACTION_PROVISION_MANAGED_PROFILE}.
+     */
+    public static final String EXTRA_PROVISIONING_ALLOW_OFFLINE =
+            "android.app.extra.PROVISIONING_ALLOW_OFFLINE";
+
+    /**
      * Action: Bugreport sharing with device owner has been accepted by the user.
      *
      * @hide
@@ -1552,6 +1579,78 @@
     public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 1 << 2;
 
     /**
+     * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and
+     * {@link #setMinimumRequiredWifiSecurityLevel(int)}: no minimum security level.
+     *
+     * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant
+     * represents the current minimum security level required.
+     * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the
+     * minimum security level a Wi-Fi network must meet.
+     *
+     * @see #WIFI_SECURITY_PERSONAL
+     * @see #WIFI_SECURITY_ENTERPRISE_EAP
+     * @see #WIFI_SECURITY_ENTERPRISE_192
+     */
+    public static final int WIFI_SECURITY_OPEN = 0;
+
+    /**
+     * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and
+     * {@link #setMinimumRequiredWifiSecurityLevel(int)}: personal network such as WEP, WPA2-PSK.
+     *
+     * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant
+     * represents the current minimum security level required.
+     * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the
+     * minimum security level a Wi-Fi network must meet.
+     *
+     * @see #WIFI_SECURITY_OPEN
+     * @see #WIFI_SECURITY_ENTERPRISE_EAP
+     * @see #WIFI_SECURITY_ENTERPRISE_192
+     */
+    public static final int WIFI_SECURITY_PERSONAL = 1;
+
+    /**
+     * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and
+     * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise EAP network.
+     *
+     * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant
+     * represents the current minimum security level required.
+     * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the
+     * minimum security level a Wi-Fi network must meet.
+     *
+     * @see #WIFI_SECURITY_OPEN
+     * @see #WIFI_SECURITY_PERSONAL
+     * @see #WIFI_SECURITY_ENTERPRISE_192
+     */
+    public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2;
+
+    /**
+     * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and
+     * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise 192 bit network.
+     *
+     * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant
+     * represents the current minimum security level required.
+     * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the
+     * minimum security level a Wi-Fi network must meet.
+     *
+     * @see #WIFI_SECURITY_OPEN
+     * @see #WIFI_SECURITY_PERSONAL
+     * @see #WIFI_SECURITY_ENTERPRISE_EAP
+     */
+    public static final int WIFI_SECURITY_ENTERPRISE_192 = 3;
+
+    /**
+     * Possible Wi-Fi minimum security levels
+     *
+     * @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_SECURITY_"}, value = {
+            WIFI_SECURITY_OPEN,
+            WIFI_SECURITY_PERSONAL,
+            WIFI_SECURITY_ENTERPRISE_EAP,
+            WIFI_SECURITY_ENTERPRISE_192})
+    public @interface WifiSecurity {}
+
+    /**
      * This MIME type is used for starting the device owner provisioning.
      *
      * <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -1697,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
@@ -2986,6 +3107,54 @@
             "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
 
     /**
+     * Activity action: attempts to establish network connection
+     *
+     * <p>This intent can be accompanied by any of the relevant provisioning extras related to
+     * network connectivity, such as:
+     * <ul>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_SSID}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_HIDDEN}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_PASSWORD}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}</li>
+     *     <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_EAP_METHOD}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_PHASE2_AUTH}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_IDENTITY}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY}</li>
+     *     <li>{@code #EXTRA_PROVISIONING_WIFI_DOMAIN}</li>
+     * </ul>
+     *
+     * <p>If there are provisioning extras related to network connectivity, this activity
+     * attempts to connect to the specified network. Otherwise it prompts the end-user to connect.
+     *
+     * <p>This activity is meant to be started by the provisioning initiator prior to starting
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} or {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}.
+     *
+     * <p>Note that network connectivity is still also handled when provisioning via {@link
+     * #ACTION_PROVISION_MANAGED_PROFILE} or {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}. {@link
+     * #ACTION_ESTABLISH_NETWORK_CONNECTION} should only be used in cases when the provisioning
+     * initiator would like to do some additional logic after the network connectivity step and
+     * before the start of provisioning.
+     *
+     * If network connection is established, {@link Activity#RESULT_OK} will be returned. Otherwise
+     * the result will be {@link Activity#RESULT_CANCELED}.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.DISPATCH_PROVISIONING_MESSAGE)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ESTABLISH_NETWORK_CONNECTION =
+            "android.app.action.ESTABLISH_NETWORK_CONNECTION";
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
@@ -3256,14 +3425,15 @@
 
     /**
      * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
-     * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated using, the updated resources
-     * can be retrieved using {@link #getDrawable}.
+     * resources with IDs {@link #EXTRA_RESOURCE_ID} has been updated, the updated resources can be
+     * retrieved using {@link #getDrawable} and {@code #getString}.
      *
      * <p>This broadcast is sent to registered receivers only.
      *
      * <p> The following extras will be included to identify the type of resource being updated:
      * <ul>
      *     <li>{@link #EXTRA_RESOURCE_TYPE_DRAWABLE} for drawable resources</li>
+     *     <li>{@link #EXTRA_RESOURCE_TYPE_STRING} for string resources</li>
      * </ul>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -3278,8 +3448,16 @@
             "android.app.extra.RESOURCE_TYPE_DRAWABLE";
 
     /**
+     * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a
+     * resource of type {@link String} is being updated.
+     */
+    public static final String EXTRA_RESOURCE_TYPE_STRING =
+            "android.app.extra.RESOURCE_TYPE_STRING";
+
+    /**
      * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which
-     * drawable IDs (see {@link DevicePolicyResources.UpdatableDrawableId}) have been updated.
+     * resource IDs (see {@link DevicePolicyResources.UpdatableDrawableId} and
+     * {@link DevicePolicyResources.UpdatableStringId}) have been updated.
      */
     public static final String EXTRA_RESOURCE_ID =
             "android.app.extra.RESOURCE_ID";
@@ -5669,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>
@@ -9864,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();
         }
@@ -9885,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) {
@@ -10522,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.
@@ -10560,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.
@@ -14477,19 +14763,118 @@
     }
 
     /**
+     * Called by device owner or profile owner of an organization-owned managed profile to
+     * specify the minimum security level required for Wi-Fi networks.
+     * The device may not connect to networks that do not meet the minimum security level.
+     * If the current network does not meet the minimum security level set, it will be disconnected.
+     *
+     *
+     * @param level minimum security level
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     *         an organization-owned managed profile.
+     */
+    public void setMinimumRequiredWifiSecurityLevel(@WifiSecurity int level) {
+        throwIfParentInstance("setMinimumRequiredWifiSecurityLevel");
+        if (mService != null) {
+            try {
+                mService.setMinimumRequiredWifiSecurityLevel(level);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the current Wi-Fi minimum security level.
+     *
+     * @see #setMinimumRequiredWifiSecurityLevel(int)
+     */
+    public @WifiSecurity int getMinimumRequiredWifiSecurityLevel() {
+        throwIfParentInstance("getMinimumRequiredWifiSecurityLevel");
+        if (mService == null) {
+            return WIFI_SECURITY_OPEN;
+        }
+        try {
+            return mService.getMinimumRequiredWifiSecurityLevel();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Called by device owner or profile owner of an organization-owned managed profile to
+     * specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}).
+     * Wi-Fi SSID policy specifies the SSID restriction the network must satisfy
+     * in order to be eligible for a connection. Providing a null policy results in the
+     * deactivation of the SSID restriction
+     *
+     * @param policy Wi-Fi SSID policy
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     *         an organization-owned managed profile.
+     */
+    public void setWifiSsidPolicy(@Nullable WifiSsidPolicy policy) {
+        throwIfParentInstance("setWifiSsidPolicy");
+        if (mService != null) {
+            try {
+                if (policy == null) {
+                    mService.setSsidAllowlist(new ArrayList<>());
+                } else {
+                    int policyType = policy.getPolicyType();
+                    if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
+                        mService.setSsidAllowlist(new ArrayList<>(policy.getSsids()));
+                    } else {
+                        mService.setSsidDenylist(new ArrayList<>(policy.getSsids()));
+                    }
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the current Wi-Fi SSID policy.
+     * If the policy has not been set, it will return NULL.
+     *
+     * @see #setWifiSsidPolicy(WifiSsidPolicy)
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     * an organization-owned managed profile or a system app.
+     */
+    @Nullable
+    public WifiSsidPolicy getWifiSsidPolicy() {
+        throwIfParentInstance("getWifiSsidPolicy");
+        if (mService == null) {
+            return null;
+        }
+        try {
+            List<String> allowlist = mService.getSsidAllowlist();
+            if (!allowlist.isEmpty()) {
+                return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(allowlist));
+            }
+            List<String> denylist = mService.getSsidDenylist();
+            if (!denylist.isEmpty()) {
+                return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(denylist));
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return null;
+    }
+
+    /**
      * 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
@@ -14509,11 +14894,6 @@
      *
      * @param drawables The list of {@link DevicePolicyDrawableResource} to update.
      *
-     * @throws IllegalArgumentException if {@link DevicePolicyDrawableResource#getDrawableId()},
-     * {@link DevicePolicyDrawableResource#getDrawableStyle()}, or
-     * {@link DevicePolicyDrawableResource#getDrawableSource()} aren't defined in
-     * {@link DevicePolicyResources.Drawable}.
-     *
      * @hide
      */
     @SystemApi
@@ -14530,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
@@ -14541,14 +14921,11 @@
      *
      * @param drawableIds The list of IDs  to remove.
      *
-     * @throws IllegalArgumentException if IDs are not defined in
-     * {@link DevicePolicyResources.Drawable}
-     *
      * @hide
      */
     @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);
@@ -14560,16 +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.
@@ -14582,20 +14962,25 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @Nullable
+    @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
+     * {@link NullPointerException} is thrown.
+     *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
      *
@@ -14605,14 +14990,19 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @Nullable
+    @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) {
@@ -14640,9 +15030,12 @@
     }
 
     /**
-     * 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
+     * {@link NullPointerException} is thrown.
+     *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
      *
@@ -14654,24 +15047,27 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @Nullable
+    @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
+     * {@link NullPointerException} is thrown.
+     *
      * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
      * notified when a resource has been updated.
      *
@@ -14684,15 +15080,20 @@
      * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for
      *                              the provided params.
      */
-    @Nullable
+    @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) {
@@ -14714,4 +15115,167 @@
         }
         return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader);
     }
+
+    /**
+     * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string
+     * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID
+     * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any
+     * system UI surface calling {@link #getString} with {@code stringId} will get
+     * the new resource after this API is called.
+     *
+     * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
+     * registered receivers when a resource has been updated successfully.
+     *
+     * <p>Important notes to consider when using this API:
+     * <ul>
+     * <li> {@link #getString} references the resource
+     * {@code callingPackageResourceId} in the calling package each time it gets called. You have to
+     * ensure that the resource is always available in the calling package as long as it is used as
+     * an updated resource.
+     * <li> You still have to re-call {@code setStrings} even if you only make changes to the
+     * content of the resource with ID {@code callingPackageResourceId} as the content might be
+     * cached and would need updating.
+     * </ul>
+     *
+     * @param strings The list of {@link DevicePolicyStringResource} to update.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setStrings(@NonNull Set<DevicePolicyStringResource> strings) {
+        if (mService != null) {
+            try {
+                mService.setStrings(new ArrayList<>(strings));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Removes the updated strings for the list of {@code stringIds} (see
+     * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings},
+     * meaning any subsequent calls to {@link #getString} for the provided IDs will
+     * return the default string from {@code defaultStringLoader}.
+     *
+     * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to
+     * registered receivers when a resource has been reset successfully.
+     *
+     * @param stringIds The list of IDs to remove the updated resources for.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetStrings(@NonNull String[] stringIds) {
+        if (mService != null) {
+            try {
+                mService.resetStrings(stringIds);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the appropriate updated string for the {@code stringId} (see
+     * {@link DevicePolicyResources.String}) if one was set using
+     * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}.
+     *
+     * <p>Also returns the string from {@code defaultStringLoader} if
+     * {@link DevicePolicyResources.String#INVALID_ID} was passed.
+     *
+     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
+     * {@link NullPointerException} is thrown.
+     *
+     * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get
+     * notified when a resource has been updated.
+     *
+     * <p>Note that each call to this API loads the resource from the package that called
+     * {@link #setStrings} to set the updated resource.
+     *
+     * @param stringId The IDs to get the updated resource for.
+     * @param defaultStringLoader To get the default string if no updated string was set for
+     *         {@code stringId}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public String getString(
+            @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
+            @NonNull Callable<String> defaultStringLoader) {
+
+        Objects.requireNonNull(stringId, "stringId can't be null");
+        Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
+
+        if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) {
+            return ParcelableResource.loadDefaultString(defaultStringLoader);
+        }
+        if (mService != null) {
+            try {
+                ParcelableResource resource = mService.getString(stringId);
+                if (resource == null) {
+                    return ParcelableResource.loadDefaultString(defaultStringLoader);
+                }
+                return resource.getString(mContext, defaultStringLoader);
+            } catch (RemoteException e) {
+                Log.e(
+                        TAG,
+                        "Error getting the updated string from DevicePolicyManagerService.",
+                        e);
+                return ParcelableResource.loadDefaultString(defaultStringLoader);
+            }
+        }
+        return ParcelableResource.loadDefaultString(defaultStringLoader);
+    }
+
+    /**
+     * Similar to {@link #getString(String, Callable)} but accepts {@code formatArgs} and returns a
+     * localized formatted string, substituting the format arguments as defined in
+     * {@link java.util.Formatter} and {@link java.lang.String#format}, (see
+     * {@link Resources#getString(int, Object...)}).
+     *
+     * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a
+     * {@link NullPointerException} is thrown.
+     *
+     * @param stringId The IDs to get the updated resource for.
+     * @param defaultStringLoader To get the default string if no updated string was set for
+     *         {@code stringId}.
+     * @param formatArgs The format arguments that will be used for substitution.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @SuppressLint("SamShouldBeLast")
+    public String getString(
+            @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
+            @NonNull Callable<String> defaultStringLoader,
+            @NonNull Object... formatArgs) {
+
+        Objects.requireNonNull(stringId, "stringId can't be null");
+        Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
+
+        if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) {
+            return ParcelableResource.loadDefaultString(defaultStringLoader);
+        }
+        if (mService != null) {
+            try {
+                ParcelableResource resource = mService.getString(stringId);
+                if (resource == null) {
+                    return ParcelableResource.loadDefaultString(defaultStringLoader);
+                }
+                return resource.getString(mContext, defaultStringLoader, formatArgs);
+            } catch (RemoteException e) {
+                Log.e(
+                        TAG,
+                        "Error getting the updated string from DevicePolicyManagerService.",
+                        e);
+                return ParcelableResource.loadDefaultString(defaultStringLoader);
+            }
+        }
+        return ParcelableResource.loadDefaultString(defaultStringLoader);
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 5133f26..2ad2010 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -16,8 +16,122 @@
 
 package android.app.admin;
 
-import android.annotation.IntDef;
+import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_SOON_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.DISABLED_BY_ADMIN_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WIDGETS_WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_FOLDER_NAME;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU_ACCEPT;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_ENABLE_BUTTON;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_DESCRIPTION;
+import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSE_BUTTON;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_BY_ADMIN_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_WORK_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.ONGOING_PRIVACY_DIALOG_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
+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.StringDef;
 import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -27,8 +141,8 @@
 /**
  * Class containing the required identifiers to update device management resources.
  *
- * <p>See {@link DevicePolicyManager#getDrawable}.
- *
+ * <p>See {@link DevicePolicyManager#getDrawable} and
+ * {@code DevicePolicyManager#getString}.
  */
 public final class DevicePolicyResources {
 
@@ -38,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 {}
 
@@ -54,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 {}
 
@@ -67,59 +182,127 @@
      * @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 {}
 
+    /**
+     * Resource identifiers used to update device management-related string resources.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+            // Launcher Strings
+            WORK_PROFILE_EDU, WORK_PROFILE_EDU_ACCEPT, Strings.Launcher.WORK_PROFILE_PAUSED_TITLE,
+            WORK_PROFILE_PAUSED_DESCRIPTION, WORK_PROFILE_PAUSE_BUTTON, WORK_PROFILE_ENABLE_BUTTON,
+            ALL_APPS_WORK_TAB, ALL_APPS_PERSONAL_TAB, ALL_APPS_WORK_TAB_ACCESSIBILITY,
+            ALL_APPS_PERSONAL_TAB_ACCESSIBILITY, WORK_FOLDER_NAME, WIDGETS_WORK_TAB,
+            WIDGETS_PERSONAL_TAB, DISABLED_BY_ADMIN_MESSAGE,
+
+            // SysUI Strings
+            QS_MSG_MANAGEMENT, QS_MSG_NAMED_MANAGEMENT, QS_MSG_MANAGEMENT_MONITORING,
+            QS_MSG_NAMED_MANAGEMENT_MONITORING, QS_MSG_MANAGEMENT_NAMED_VPN,
+            QS_MSG_NAMED_MANAGEMENT_NAMED_VPN, QS_MSG_MANAGEMENT_MULTIPLE_VPNS,
+            QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS, QS_MSG_WORK_PROFILE_MONITORING,
+            QS_MSG_NAMED_WORK_PROFILE_MONITORING, QS_MSG_WORK_PROFILE_NETWORK,
+            QS_MSG_WORK_PROFILE_NAMED_VPN, QS_MSG_PERSONAL_PROFILE_NAMED_VPN,
+            QS_DIALOG_MANAGEMENT_TITLE, QS_DIALOG_VIEW_POLICIES, QS_DIALOG_MANAGEMENT,
+            QS_DIALOG_NAMED_MANAGEMENT, QS_DIALOG_MANAGEMENT_CA_CERT,
+            QS_DIALOG_WORK_PROFILE_CA_CERT, QS_DIALOG_MANAGEMENT_NETWORK,
+            QS_DIALOG_WORK_PROFILE_NETWORK, QS_DIALOG_MANAGEMENT_NAMED_VPN,
+            QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN, QS_DIALOG_WORK_PROFILE_NAMED_VPN,
+            QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN, BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT,
+            BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT, BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT,
+            BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS, STATUS_BAR_WORK_ICON_ACCESSIBILITY,
+            ONGOING_PRIVACY_DIALOG_WORK, KEYGUARD_MANAGEMENT_DISCLOSURE,
+            KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, WORK_LOCK_ACCESSIBILITY,
+
+            // Core Strings
+            WORK_PROFILE_DELETED_TITLE, WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE,
+            WORK_PROFILE_DELETED_GENERIC_MESSAGE, WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE,
+            PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE,
+            PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
+            PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE,
+            NETWORK_LOGGING_TITLE,  NETWORK_LOGGING_MESSAGE,
+            NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN,
+            SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK,
+            FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB,
+            RESOLVER_WORK_TAB, RESOLVER_PERSONAL_TAB_ACCESSIBILITY, RESOLVER_WORK_TAB_ACCESSIBILITY,
+            RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, RESOLVER_CANT_SHARE_WITH_PERSONAL,
+            RESOLVER_CANT_SHARE_WITH_WORK, RESOLVER_CANT_ACCESS_PERSONAL, RESOLVER_CANT_ACCESS_WORK,
+            RESOLVER_WORK_PAUSED_TITLE, RESOLVER_NO_WORK_APPS, RESOLVER_NO_PERSONAL_APPS,
+            CANT_ADD_ACCOUNT_MESSAGE, PACKAGE_INSTALLED_BY_DO, PACKAGE_UPDATED_BY_DO,
+            PACKAGE_DELETED_BY_DO, UNLAUNCHABLE_APP_WORK_PAUSED_TITLE,
+            UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, PROFILE_ENCRYPTED_TITLE, PROFILE_ENCRYPTED_DETAIL,
+            PROFILE_ENCRYPTED_MESSAGE, WORK_PROFILE_BADGED_LABEL,
+
+            // DocsUi Strings
+            WORK_PROFILE_OFF_ERROR_TITLE, WORK_PROFILE_OFF_ENABLE_BUTTON,
+            CANT_SELECT_WORK_FILES_TITLE, CANT_SELECT_WORK_FILES_MESSAGE,
+            CANT_SELECT_PERSONAL_FILES_TITLE, CANT_SELECT_PERSONAL_FILES_MESSAGE,
+            CANT_SAVE_TO_WORK_TITLE, CANT_SAVE_TO_WORK_MESSAGE, CANT_SAVE_TO_PERSONAL_TITLE,
+            CANT_SAVE_TO_PERSONAL_MESSAGE, CROSS_PROFILE_NOT_ALLOWED_TITLE,
+            CROSS_PROFILE_NOT_ALLOWED_MESSAGE, PREVIEW_WORK_FILE_ACCESSIBILITY, PERSONAL_TAB,
+            WORK_TAB, WORK_ACCESSIBILITY,
+
+            // MediaProvider Strings
+            SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE,
+            BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE,
+            BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE,
+            WORK_PROFILE_PAUSED_MESSAGE
+    })
+    public @interface UpdatableStringId {
+    }
 
     /**
      * 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);
@@ -140,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);
@@ -207,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);
@@ -240,4 +423,995 @@
             }
         }
     }
+
+    /**
+     * Class containing the identifiers used to update device management-related system strings.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Strings {
+
+        private Strings() {}
+
+        /**
+         * An ID for any string that can't be updated.
+         */
+        public static final String UNDEFINED = "UNDEFINED";
+
+        /**
+         * @hide
+         */
+        public static final Set<String> UPDATABLE_STRING_IDS = buildStringsSet();
+
+        private static Set<String> buildStringsSet() {
+            Set<String> strings = new HashSet<>();
+            strings.addAll(Launcher.buildStringsSet());
+            strings.addAll(SystemUi.buildStringsSet());
+            strings.addAll(Core.buildStringsSet());
+            strings.addAll(DocumentsUi.buildStringsSet());
+            strings.addAll(MediaProvider.buildStringsSet());
+            return strings;
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the Launcher package.
+         *
+         * @hide
+         */
+        public static final class Launcher {
+
+            private Launcher(){}
+
+            private static final String PREFIX = "Launcher.";
+
+            /**
+             * User on-boarding title for work profile apps.
+             */
+            public static final String WORK_PROFILE_EDU = PREFIX + "WORK_PROFILE_EDU";
+
+            /**
+             * Action label to finish work profile edu.
+             */
+            public static final String WORK_PROFILE_EDU_ACCEPT = PREFIX + "WORK_PROFILE_EDU_ACCEPT";
+
+            /**
+             * Title shown when user opens work apps tab while work profile is paused.
+             */
+            public static final String WORK_PROFILE_PAUSED_TITLE =
+                    PREFIX + "WORK_PROFILE_PAUSED_TITLE";
+
+            /**
+             * Description shown when user opens work apps tab while work profile is paused.
+             */
+            public static final String WORK_PROFILE_PAUSED_DESCRIPTION =
+                    PREFIX + "WORK_PROFILE_PAUSED_DESCRIPTION";
+
+            /**
+             * Shown on the button to pause work profile.
+             */
+            public static final String WORK_PROFILE_PAUSE_BUTTON =
+                    PREFIX + "WORK_PROFILE_PAUSE_BUTTON";
+
+            /**
+             * Shown on the button to enable work profile.
+             */
+            public static final String WORK_PROFILE_ENABLE_BUTTON =
+                    PREFIX + "WORK_PROFILE_ENABLE_BUTTON";
+
+            /**
+             * Label on launcher tab to indicate work apps.
+             */
+            public static final String ALL_APPS_WORK_TAB = PREFIX + "ALL_APPS_WORK_TAB";
+
+            /**
+             * Label on launcher tab to indicate personal apps.
+             */
+            public static final String ALL_APPS_PERSONAL_TAB = PREFIX + "ALL_APPS_PERSONAL_TAB";
+
+            /**
+             * Accessibility description for launcher tab to indicate work apps.
+             */
+            public static final String ALL_APPS_WORK_TAB_ACCESSIBILITY =
+                    PREFIX + "ALL_APPS_WORK_TAB_ACCESSIBILITY";
+
+            /**
+             * Accessibility description for launcher tab to indicate personal apps.
+             */
+            public static final String ALL_APPS_PERSONAL_TAB_ACCESSIBILITY =
+                    PREFIX + "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY";
+
+            /**
+             * Work folder name.
+             */
+            public static final String WORK_FOLDER_NAME = PREFIX + "WORK_FOLDER_NAME";
+
+            /**
+             * Label on widget tab to indicate work app widgets.
+             */
+            public static final String WIDGETS_WORK_TAB = PREFIX + "WIDGETS_WORK_TAB";
+
+            /**
+             * Label on widget tab to indicate personal app widgets.
+             */
+            public static final String WIDGETS_PERSONAL_TAB = PREFIX + "WIDGETS_PERSONAL_TAB";
+
+            /**
+             * Message shown when a feature is disabled by the admin (e.g. changing wallpaper).
+             */
+            public static final String DISABLED_BY_ADMIN_MESSAGE =
+                    PREFIX + "DISABLED_BY_ADMIN_MESSAGE";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(WORK_PROFILE_EDU);
+                strings.add(WORK_PROFILE_EDU_ACCEPT);
+                strings.add(WORK_PROFILE_PAUSED_TITLE);
+                strings.add(WORK_PROFILE_PAUSED_DESCRIPTION);
+                strings.add(WORK_PROFILE_PAUSE_BUTTON);
+                strings.add(WORK_PROFILE_ENABLE_BUTTON);
+                strings.add(ALL_APPS_WORK_TAB);
+                strings.add(ALL_APPS_PERSONAL_TAB);
+                strings.add(ALL_APPS_PERSONAL_TAB_ACCESSIBILITY);
+                strings.add(ALL_APPS_WORK_TAB_ACCESSIBILITY);
+                strings.add(WORK_FOLDER_NAME);
+                strings.add(WIDGETS_WORK_TAB);
+                strings.add(WIDGETS_PERSONAL_TAB);
+                strings.add(DISABLED_BY_ADMIN_MESSAGE);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the SystemUi package.
+         *
+         * @hide
+         */
+        public static final class SystemUi {
+
+            private SystemUi() {
+            }
+            private static final String PREFIX = "SystemUi.";
+
+            /**
+             * Label in quick settings for toggling work profile on/off.
+             */
+            public static final String QS_WORK_PROFILE_LABEL = PREFIX + "QS_WORK_PROFILE_LABEL";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate device management.
+             */
+            public static final String QS_MSG_MANAGEMENT = PREFIX + "QS_MSG_MANAGEMENT";
+
+            /**
+             * Similar to {@link #QS_MSG_MANAGEMENT} but accepts the organization name as a
+             * param.
+             */
+            public static final String QS_MSG_NAMED_MANAGEMENT = PREFIX + "QS_MSG_NAMED_MANAGEMENT";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate device management monitoring.
+             */
+            public static final String QS_MSG_MANAGEMENT_MONITORING =
+                    PREFIX + "QS_MSG_MANAGEMENT_MONITORING";
+
+            /**
+             * Similar to {@link #QS_MSG_MANAGEMENT_MONITORING} but accepts the
+             * organization name as a param.
+             */
+            public static final String QS_MSG_NAMED_MANAGEMENT_MONITORING =
+                    PREFIX + "QS_MSG_NAMED_MANAGEMENT_MONITORING";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate device management and the
+             * device is connected to a VPN, accepts VPN name as a param.
+             */
+            public static final String QS_MSG_MANAGEMENT_NAMED_VPN =
+                    PREFIX + "QS_MSG_MANAGEMENT_NAMED_VPN";
+
+            /**
+             * Similar to {@link #QS_MSG_MANAGEMENT_NAMED_VPN} but also accepts the
+             * organization name as a param.
+             */
+            public static final String QS_MSG_NAMED_MANAGEMENT_NAMED_VPN =
+                    PREFIX + "QS_MSG_NAMED_MANAGEMENT_NAMED_VPN";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate device management and the
+             * device is connected to multiple VPNs.
+             */
+            public static final String QS_MSG_MANAGEMENT_MULTIPLE_VPNS =
+                    PREFIX + "QS_MSG_MANAGEMENT_MULTIPLE_VPNS";
+
+            /**
+             * Similar to {@link #QS_MSG_MANAGEMENT_MULTIPLE_VPNS} but also accepts the
+             * organization name as a param.
+             */
+            public static final String QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS =
+                    PREFIX + "QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate work profile monitoring.
+             */
+            public static final String QS_MSG_WORK_PROFILE_MONITORING =
+                    PREFIX + "QS_MSG_WORK_PROFILE_MONITORING";
+
+            /**
+             * Similar to {@link #QS_MSG_WORK_PROFILE_MONITORING} but accepts the
+             * organization name as a param.
+             */
+            public static final String QS_MSG_NAMED_WORK_PROFILE_MONITORING =
+                    PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING";
+
+            /**
+            * Disclosure at the bottom of Quick Settings to indicate network activity is visible to
+             * admin.
+            */
+            public static final String QS_MSG_WORK_PROFILE_NETWORK =
+                    PREFIX + "QS_MSG_WORK_PROFILE_NETWORK";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate work profile is connected to a
+             * VPN, accepts VPN name as a param.
+             */
+            public static final String QS_MSG_WORK_PROFILE_NAMED_VPN =
+                    PREFIX + "QS_MSG_WORK_PROFILE_NAMED_VPN";
+
+            /**
+             * Disclosure at the bottom of Quick Settings to indicate personal profile is connected
+             * to a VPN, accepts VPN name as a param.
+             */
+            public static final String QS_MSG_PERSONAL_PROFILE_NAMED_VPN =
+                    PREFIX + "QS_MSG_PERSONAL_PROFILE_NAMED_VPN";
+
+            /**
+             * Title for dialog to indicate device management.
+             */
+            public static final String QS_DIALOG_MANAGEMENT_TITLE =
+                    PREFIX + "QS_DIALOG_MANAGEMENT_TITLE";
+
+            /**
+             * Label for button in the device management dialog to open a page with more information
+             * on the admin's abilities.
+             */
+            public static final String QS_DIALOG_VIEW_POLICIES =
+                    PREFIX + "QS_DIALOG_VIEW_POLICIES";
+
+            /**
+             * Description for device management dialog to indicate admin abilities.
+             */
+            public static final String QS_DIALOG_MANAGEMENT = PREFIX + "QS_DIALOG_MANAGEMENT";
+
+            /**
+             * Similar to {@link #QS_DIALOG_MANAGEMENT} but accepts the organization name as a
+             * param.
+             */
+            public static final String QS_DIALOG_NAMED_MANAGEMENT =
+                    PREFIX + "QS_DIALOG_NAMED_MANAGEMENT";
+
+            /**
+             * Description for the managed device certificate authorities in the device management
+             * dialog.
+             */
+            public static final String QS_DIALOG_MANAGEMENT_CA_CERT =
+                    PREFIX + "QS_DIALOG_MANAGEMENT_CA_CERT";
+
+            /**
+             * Description for the work profile certificate authorities in the device management
+             * dialog.
+             */
+            public static final String QS_DIALOG_WORK_PROFILE_CA_CERT =
+                    PREFIX + "QS_DIALOG_WORK_PROFILE_CA_CERT";
+
+            /**
+             * Description for the managed device network logging in the device management dialog.
+             */
+            public static final String QS_DIALOG_MANAGEMENT_NETWORK =
+                    PREFIX + "QS_DIALOG_MANAGEMENT_NETWORK";
+
+            /**
+             * Description for the work profile network logging in the device management dialog.
+             */
+            public static final String QS_DIALOG_WORK_PROFILE_NETWORK =
+                    PREFIX + "QS_DIALOG_WORK_PROFILE_NETWORK";
+
+            /**
+             * Description for an active VPN in the device management dialog, accepts VPN name as a
+             * param.
+             */
+            public static final String QS_DIALOG_MANAGEMENT_NAMED_VPN =
+                    PREFIX + "QS_DIALOG_MANAGEMENT_NAMED_VPN";
+
+            /**
+             * Description for two active VPN in the device management dialog, accepts two VPN names
+             * as params.
+             */
+            public static final String QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN =
+                    PREFIX + "QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN";
+
+            /**
+             * Description for an active work profile VPN in the device management dialog, accepts
+             * VPN name as a param.
+             */
+            public static final String QS_DIALOG_WORK_PROFILE_NAMED_VPN =
+                    PREFIX + "QS_DIALOG_WORK_PROFILE_NAMED_VPN";
+
+            /**
+             * Description for an active personal profile VPN in the device management dialog,
+             * accepts VPN name as a param.
+             */
+            public static final String QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN =
+                    PREFIX + "QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN";
+
+            /**
+             * Content of a dialog shown when the user only has one attempt left to provide the
+             * correct pin before the work profile is removed.
+             */
+            public static final String BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT =
+                    PREFIX + "BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT";
+
+            /**
+             * Content of a dialog shown when the user only has one attempt left to provide the
+             * correct pattern before the work profile is removed.
+             */
+            public static final String BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT =
+                    PREFIX + "BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT";
+
+            /**
+             * Content of a dialog shown when the user only has one attempt left to provide the
+             * correct password before the work profile is removed.
+             */
+            public static final String BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT =
+                    PREFIX + "BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT";
+
+            /**
+             * Content of a dialog shown when the user has failed to provide the work lock too many
+             * times and the work profile is removed.
+             */
+            public static final String BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS =
+                    PREFIX + "BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS";
+
+            /**
+             * Accessibility label for managed profile icon in the status bar
+             */
+            public static final String STATUS_BAR_WORK_ICON_ACCESSIBILITY =
+                    PREFIX + "STATUS_BAR_WORK_ICON_ACCESSIBILITY";
+
+            /**
+             * Text appended to privacy dialog, indicating that the application is in the work
+             * profile.
+             */
+            public static final String ONGOING_PRIVACY_DIALOG_WORK =
+                    PREFIX + "ONGOING_PRIVACY_DIALOG_WORK";
+
+            /**
+             * Text on keyguard screen indicating device management.
+             */
+            public static final String KEYGUARD_MANAGEMENT_DISCLOSURE =
+                    PREFIX + "KEYGUARD_MANAGEMENT_DISCLOSURE";
+
+            /**
+             * Similar to {@link #KEYGUARD_MANAGEMENT_DISCLOSURE} but also accepts organization name
+             * as a param.
+             */
+            public static final String KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE =
+                    PREFIX + "KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE";
+
+            /**
+             * Content description for the work profile lock screen.
+             */
+            public static final String WORK_LOCK_ACCESSIBILITY = PREFIX + "WORK_LOCK_ACCESSIBILITY";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(QS_WORK_PROFILE_LABEL);
+                strings.add(QS_MSG_MANAGEMENT);
+                strings.add(QS_MSG_NAMED_MANAGEMENT);
+                strings.add(QS_MSG_MANAGEMENT_MONITORING);
+                strings.add(QS_MSG_NAMED_MANAGEMENT_MONITORING);
+                strings.add(QS_MSG_MANAGEMENT_NAMED_VPN);
+                strings.add(QS_MSG_NAMED_MANAGEMENT_NAMED_VPN);
+                strings.add(QS_MSG_MANAGEMENT_MULTIPLE_VPNS);
+                strings.add(QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS);
+                strings.add(QS_MSG_WORK_PROFILE_MONITORING);
+                strings.add(QS_MSG_NAMED_WORK_PROFILE_MONITORING);
+                strings.add(QS_MSG_WORK_PROFILE_NETWORK);
+                strings.add(QS_MSG_WORK_PROFILE_NAMED_VPN);
+                strings.add(QS_MSG_PERSONAL_PROFILE_NAMED_VPN);
+                strings.add(QS_DIALOG_MANAGEMENT_TITLE);
+                strings.add(QS_DIALOG_VIEW_POLICIES);
+                strings.add(QS_DIALOG_MANAGEMENT);
+                strings.add(QS_DIALOG_NAMED_MANAGEMENT);
+                strings.add(QS_DIALOG_MANAGEMENT_CA_CERT);
+                strings.add(QS_DIALOG_WORK_PROFILE_CA_CERT);
+                strings.add(QS_DIALOG_MANAGEMENT_NETWORK);
+                strings.add(QS_DIALOG_WORK_PROFILE_NETWORK);
+                strings.add(QS_DIALOG_MANAGEMENT_NAMED_VPN);
+                strings.add(QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN);
+                strings.add(QS_DIALOG_WORK_PROFILE_NAMED_VPN);
+                strings.add(QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN);
+                strings.add(BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT);
+                strings.add(BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT);
+                strings.add(BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT);
+                strings.add(BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS);
+                strings.add(STATUS_BAR_WORK_ICON_ACCESSIBILITY);
+                strings.add(ONGOING_PRIVACY_DIALOG_WORK);
+                strings.add(KEYGUARD_MANAGEMENT_DISCLOSURE);
+                strings.add(KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE);
+                strings.add(WORK_LOCK_ACCESSIBILITY);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the android core package.
+         *
+         * @hide
+         */
+        public static final class Core {
+
+            private Core() {
+            }
+
+            private static final String PREFIX = "Core.";
+            /**
+             * Notification title when the system deletes the work profile.
+             */
+            public static final String WORK_PROFILE_DELETED_TITLE =
+                    PREFIX + "WORK_PROFILE_DELETED_TITLE";
+
+            /**
+             * Content text for the "Work profile deleted" notification to indicates that a work
+             * profile has been deleted because the maximum failed password attempts as been
+             * reached.
+             */
+            public static final String WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE =
+                    PREFIX + "WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE";
+
+            /**
+             * Content text for the "Work profile deleted" notification to indicate that a work
+             * profile has been deleted.
+             */
+            public static final String WORK_PROFILE_DELETED_GENERIC_MESSAGE =
+                    PREFIX + "WORK_PROFILE_DELETED_GENERIC_MESSAGE";
+
+            /**
+             * Content text for the "Work profile deleted" notification to indicates that a work
+             * profile has been deleted because the admin of an organization-owned device has
+             * relinquishes it.
+             */
+            public static final String WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE =
+                    PREFIX + "WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE";
+
+            /**
+             * Notification title for when personal apps are either blocked or will be blocked
+             * soon due to a work policy from their admin.
+             */
+            public static final String PERSONAL_APP_SUSPENSION_TITLE =
+                    PREFIX + "PERSONAL_APP_SUSPENSION_TITLE";
+
+            /**
+             * Content text for the personal app suspension notification to indicate that personal
+             * apps are blocked due to a work policy from the admin.
+             */
+            public static final String PERSONAL_APP_SUSPENSION_MESSAGE =
+                    PREFIX + "PERSONAL_APP_SUSPENSION_MESSAGE";
+
+            /**
+             * Content text for the personal app suspension notification to indicate that personal
+             * apps will be blocked at a particular time due to a work policy from their admin.
+             * It also explains for how many days the profile is allowed to be off.
+             * <ul>Takes in the following as params:
+             * <li> The date that the personal apps will get suspended at</li>
+             * <li> The time that the personal apps will get suspended at</li>
+             * <li> The max allowed days for the work profile stay switched off</li>
+             * </ul>
+             */
+            public static final String PERSONAL_APP_SUSPENSION_SOON_MESSAGE =
+                    PREFIX + "PERSONAL_APP_SUSPENSION_SOON_MESSAGE";
+
+            /**
+             * Title for the button that turns work profile in the personal app suspension
+             * notification.
+             */
+            public static final String PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE =
+                    PREFIX + "PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE";
+
+            /**
+             * A toast message displayed when printing is attempted but disabled by policy, accepts
+             * admin name as a param.
+             */
+            public static final String PRINTING_DISABLED_NAMED_ADMIN =
+                    PREFIX + "PRINTING_DISABLED_NAMED_ADMIN";
+
+            /**
+             * Notification title to indicate that the device owner has changed the location
+             * settings.
+             */
+            public static final String LOCATION_CHANGED_TITLE = PREFIX + "LOCATION_CHANGED_TITLE";
+
+            /**
+             * Content text for the location changed notification to indicate that the device owner
+             * has changed the location settings.
+             */
+            public static final String LOCATION_CHANGED_MESSAGE =
+                    PREFIX + "LOCATION_CHANGED_MESSAGE";
+
+            /**
+             * Notification title to indicate that the device is managed and network logging was
+             * activated by a device owner.
+             */
+            public static final String NETWORK_LOGGING_TITLE = PREFIX + "NETWORK_LOGGING_TITLE";
+
+            /**
+             * Content text for the network logging notification to indicate that the device is
+             * managed and network logging was activated by a device owner.
+             */
+            public static final String NETWORK_LOGGING_MESSAGE = PREFIX + "NETWORK_LOGGING_MESSAGE";
+
+            /**
+             * Content description of the work profile icon in the notifications.
+             */
+            public static final String NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION =
+                    PREFIX + "NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION";
+
+            /**
+             * Notification channel name for high-priority alerts from the user's IT admin for key
+             * updates about the device.
+             */
+            public static final String NOTIFICATION_CHANNEL_DEVICE_ADMIN =
+                    PREFIX + "NOTIFICATION_CHANNEL_DEVICE_ADMIN";
+
+            /**
+             * Label returned from
+             * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)}
+             * that calling app can show to user for the semantic of switching to work profile.
+             */
+            public static final String SWITCH_TO_WORK_LABEL = PREFIX + "SWITCH_TO_WORK_LABEL";
+
+            /**
+             * Label returned from
+             * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)}
+             * that calling app can show to user for the semantic of switching to personal profile.
+             */
+            public static final String SWITCH_TO_PERSONAL_LABEL =
+                    PREFIX + "SWITCH_TO_PERSONAL_LABEL";
+
+            /**
+             * Message to show when an intent automatically switches users into the work profile.
+             */
+            public static final String FORWARD_INTENT_TO_WORK = PREFIX + "FORWARD_INTENT_TO_WORK";
+
+            /**
+             * Message to show when an intent automatically switches users into the personal
+             * profile.
+             */
+            public static final String FORWARD_INTENT_TO_PERSONAL =
+                    PREFIX + "FORWARD_INTENT_TO_PERSONAL";
+
+            /**
+             * Text for the toast that is shown when the user clicks on a launcher that doesn't
+             * support the work profile, takes in the launcher name as a param.
+             */
+            public static final String RESOLVER_WORK_PROFILE_NOT_SUPPORTED =
+                    PREFIX + "RESOLVER_WORK_PROFILE_NOT_SUPPORTED";
+
+            /**
+             * Label for the personal tab in the {@link com.android.internal.app.ResolverActivity).
+             */
+            public static final String RESOLVER_PERSONAL_TAB = PREFIX + "RESOLVER_PERSONAL_TAB";
+
+            /**
+             * Label for the work tab in the {@link com.android.internal.app.ResolverActivity).
+             */
+            public static final String RESOLVER_WORK_TAB = PREFIX + "RESOLVER_WORK_TAB";
+
+            /**
+             * Accessibility Label for the personal tab in the
+             * {@link com.android.internal.app.ResolverActivity).
+             */
+            public static final String RESOLVER_PERSONAL_TAB_ACCESSIBILITY =
+                    PREFIX + "RESOLVER_PERSONAL_TAB_ACCESSIBILITY";
+
+            /**
+             * Accessibility Label for the work tab in the
+             * {@link com.android.internal.app.ResolverActivity).
+             */
+            public static final String RESOLVER_WORK_TAB_ACCESSIBILITY =
+                    PREFIX + "RESOLVER_WORK_TAB_ACCESSIBILITY";
+
+            /**
+             * Title for resolver screen to let the user know that their IT admin doesn't allow
+             * them to share this content across profiles.
+             */
+            public static final String RESOLVER_CROSS_PROFILE_BLOCKED_TITLE =
+                    PREFIX + "RESOLVER_CROSS_PROFILE_BLOCKED_TITLE";
+
+            /**
+             * Description for resolver screen to let the user know that their IT admin doesn't
+             * allow them to share this content with apps in their personal profile.
+             */
+            public static final String RESOLVER_CANT_SHARE_WITH_PERSONAL =
+                    PREFIX + "RESOLVER_CANT_SHARE_WITH_PERSONAL";
+
+            /**
+             * Description for resolver screen to let the user know that their IT admin doesn't
+             * allow them to share this content with apps in their work profile.
+             */
+            public static final String RESOLVER_CANT_SHARE_WITH_WORK =
+                    PREFIX + "RESOLVER_CANT_SHARE_WITH_WORK";
+
+            /**
+             * Description for resolver screen to let the user know that their IT admin doesn't
+             * allow them to open this specific content with an app in their personal profile.
+             */
+            public static final String RESOLVER_CANT_ACCESS_PERSONAL =
+                    PREFIX + "RESOLVER_CANT_ACCESS_PERSONAL";
+
+            /**
+             * Description for resolver screen to let the user know that their IT admin doesn't
+             * allow them to open this specific content with an app in their work profile.
+             */
+            public static final String RESOLVER_CANT_ACCESS_WORK =
+                    PREFIX + "RESOLVER_CANT_ACCESS_WORK";
+
+            /**
+             * Title for resolver screen to let the user know that they need to turn on work apps
+             * in order to share or open content
+             */
+            public static final String RESOLVER_WORK_PAUSED_TITLE =
+                    PREFIX + "RESOLVER_WORK_PAUSED_TITLE";
+
+            /**
+             * Text on resolver screen to let the user know that their current work apps don't
+             * support the specific content.
+             */
+            public static final String RESOLVER_NO_WORK_APPS = PREFIX + "RESOLVER_NO_WORK_APPS";
+
+            /**
+             * Text on resolver screen to let the user know that their current personal apps don't
+             * support the specific content.
+             */
+            public static final String RESOLVER_NO_PERSONAL_APPS =
+                    PREFIX + "RESOLVER_NO_PERSONAL_APPS";
+
+            /**
+             * Message informing user that the adding the account is disallowed by an administrator.
+             */
+            public static final String CANT_ADD_ACCOUNT_MESSAGE =
+                    PREFIX + "CANT_ADD_ACCOUNT_MESSAGE";
+
+            /**
+             * Notification shown when device owner silently installs a package.
+             */
+            public static final String PACKAGE_INSTALLED_BY_DO = PREFIX + "PACKAGE_INSTALLED_BY_DO";
+
+            /**
+             * Notification shown when device owner silently updates a package.
+             */
+            public static final String PACKAGE_UPDATED_BY_DO = PREFIX + "PACKAGE_UPDATED_BY_DO";
+
+            /**
+             * Notification shown when device owner silently deleted a package.
+             */
+            public static final String PACKAGE_DELETED_BY_DO = PREFIX + "PACKAGE_DELETED_BY_DO";
+
+            /**
+             * Title for dialog shown when user tries to open a work app when the work profile is
+             * turned off, confirming that the user wants to turn on access to their
+             * work apps.
+             */
+            public static final String UNLAUNCHABLE_APP_WORK_PAUSED_TITLE =
+                    PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE";
+
+            /**
+             * Text for dialog shown when user tries to open a work app when the work profile is
+             * turned off, confirming that the user wants to turn on access to their
+             * work apps.
+             */
+            public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE =
+                    PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE";
+
+            /**
+             * Notification title shown when work profile is credential encrypted and requires
+             * the user to unlock before it's usable.
+             */
+            public static final String PROFILE_ENCRYPTED_TITLE = PREFIX + "PROFILE_ENCRYPTED_TITLE";
+
+            /**
+             * Notification detail shown when work profile is credential encrypted and requires
+             * the user to unlock before it's usable.
+             */
+            public static final String PROFILE_ENCRYPTED_DETAIL =
+                    PREFIX + "PROFILE_ENCRYPTED_DETAIL";
+
+            /**
+             * Notification message shown when work profile is credential encrypted and requires
+             * the user to unlock before it's usable.
+             */
+            public static final String PROFILE_ENCRYPTED_MESSAGE =
+                    PREFIX + "PROFILE_ENCRYPTED_MESSAGE";
+
+            /**
+             * Used to badge a string with "Work" for work profile content, e.g. "Work Email".
+             * Accepts the string to badge as an argument.
+             * <p>See {@link android.content.pm.PackageManager#getUserBadgedLabel}</p>
+             */
+            public static final String WORK_PROFILE_BADGED_LABEL =
+                    PREFIX + "WORK_PROFILE_BADGED_LABEL";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(WORK_PROFILE_DELETED_TITLE);
+                strings.add(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE);
+                strings.add(WORK_PROFILE_DELETED_GENERIC_MESSAGE);
+                strings.add(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE);
+                strings.add(PERSONAL_APP_SUSPENSION_TITLE);
+                strings.add(PERSONAL_APP_SUSPENSION_MESSAGE);
+                strings.add(PERSONAL_APP_SUSPENSION_SOON_MESSAGE);
+                strings.add(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE);
+                strings.add(PRINTING_DISABLED_NAMED_ADMIN);
+                strings.add(LOCATION_CHANGED_TITLE);
+                strings.add(LOCATION_CHANGED_MESSAGE);
+                strings.add(NETWORK_LOGGING_TITLE);
+                strings.add(NETWORK_LOGGING_MESSAGE);
+                strings.add(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION);
+                strings.add(NOTIFICATION_CHANNEL_DEVICE_ADMIN);
+                strings.add(SWITCH_TO_WORK_LABEL);
+                strings.add(SWITCH_TO_PERSONAL_LABEL);
+                strings.add(FORWARD_INTENT_TO_WORK);
+                strings.add(FORWARD_INTENT_TO_PERSONAL);
+                strings.add(RESOLVER_WORK_PROFILE_NOT_SUPPORTED);
+                strings.add(RESOLVER_PERSONAL_TAB);
+                strings.add(RESOLVER_WORK_TAB);
+                strings.add(RESOLVER_PERSONAL_TAB_ACCESSIBILITY);
+                strings.add(RESOLVER_WORK_TAB_ACCESSIBILITY);
+                strings.add(RESOLVER_CROSS_PROFILE_BLOCKED_TITLE);
+                strings.add(RESOLVER_CANT_SHARE_WITH_PERSONAL);
+                strings.add(RESOLVER_CANT_SHARE_WITH_WORK);
+                strings.add(RESOLVER_CANT_ACCESS_PERSONAL);
+                strings.add(RESOLVER_CANT_ACCESS_WORK);
+                strings.add(RESOLVER_WORK_PAUSED_TITLE);
+                strings.add(RESOLVER_NO_WORK_APPS);
+                strings.add(RESOLVER_NO_PERSONAL_APPS);
+                strings.add(CANT_ADD_ACCOUNT_MESSAGE);
+                strings.add(PACKAGE_INSTALLED_BY_DO);
+                strings.add(PACKAGE_UPDATED_BY_DO);
+                strings.add(PACKAGE_DELETED_BY_DO);
+                strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_TITLE);
+                strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE);
+                strings.add(PROFILE_ENCRYPTED_TITLE);
+                strings.add(PROFILE_ENCRYPTED_DETAIL);
+                strings.add(PROFILE_ENCRYPTED_MESSAGE);
+                strings.add(WORK_PROFILE_BADGED_LABEL);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the DocumentsUi package.
+         */
+        public static final class DocumentsUi {
+
+            private DocumentsUi() {
+            }
+
+            private static final String PREFIX = "DocumentsUi.";
+
+            /**
+             * Title for error message shown when work profile is turned off.
+             */
+            public static final String WORK_PROFILE_OFF_ERROR_TITLE =
+                    PREFIX + "WORK_PROFILE_OFF_ERROR_TITLE";
+
+            /**
+             * Button text shown when work profile is turned off.
+             */
+            public static final String WORK_PROFILE_OFF_ENABLE_BUTTON =
+                    PREFIX + "WORK_PROFILE_OFF_ENABLE_BUTTON";
+
+            /**
+             * Title for error message shown when a user's IT admin does not allow the user to
+             * select work files from a personal app.
+             */
+            public static final String CANT_SELECT_WORK_FILES_TITLE =
+                    PREFIX + "CANT_SELECT_WORK_FILES_TITLE";
+
+            /**
+             * Message shown when a user's IT admin does not allow the user to select work files
+             * from a personal app.
+             */
+            public static final String CANT_SELECT_WORK_FILES_MESSAGE =
+                    PREFIX + "CANT_SELECT_WORK_FILES_MESSAGE";
+
+            /**
+             * Title for error message shown when a user's IT admin does not allow the user to
+             * select personal files from a work app.
+             */
+            public static final String CANT_SELECT_PERSONAL_FILES_TITLE =
+                    PREFIX + "CANT_SELECT_PERSONAL_FILES_TITLE";
+
+            /**
+             * Message shown when a user's IT admin does not allow the user to select personal files
+             * from a work app.
+             */
+            public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE =
+                    PREFIX + "CANT_SELECT_PERSONAL_FILES_MESSAGE";
+
+            /**
+             * Title for error message shown when a user's IT admin does not allow the user to save
+             * files from their personal profile to their work profile.
+             */
+            public static final String CANT_SAVE_TO_WORK_TITLE =
+                    PREFIX + "CANT_SAVE_TO_WORK_TITLE";
+
+            /**
+             * Message shown when a user's IT admin does not allow the user to save files from their
+             * personal profile to their work profile.
+             */
+            public static final String CANT_SAVE_TO_WORK_MESSAGE =
+                    PREFIX + "CANT_SAVE_TO_WORK_MESSAGE";
+
+            /**
+             * Title for error message shown when a user's IT admin does not allow the user to save
+             * files from their work profile to their personal profile.
+             */
+            public static final String CANT_SAVE_TO_PERSONAL_TITLE =
+                    PREFIX + "CANT_SAVE_TO_PERSONAL_TITLE";
+
+            /**
+             * Message shown when a user's IT admin does not allow the user to save files from their
+             * work profile to their personal profile.
+             */
+            public static final String CANT_SAVE_TO_PERSONAL_MESSAGE =
+                    PREFIX + "CANT_SAVE_TO_PERSONAL_MESSAGE";
+
+            /**
+             * Title for error message shown when a user tries to do something on their work
+             * device, but that action isn't allowed by their IT admin.
+             */
+            public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE =
+                    PREFIX + "CROSS_PROFILE_NOT_ALLOWED_TITLE";
+
+            /**
+             * Message shown when a user tries to do something on their work device, but that action
+             * isn't allowed by their IT admin.
+             */
+            public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE =
+                    PREFIX + "CROSS_PROFILE_NOT_ALLOWED_MESSAGE";
+
+            /**
+             * Content description text that's spoken by a screen reader for previewing a work file
+             * before opening it. Accepts file name as a param.
+             */
+            public static final String PREVIEW_WORK_FILE_ACCESSIBILITY =
+                    PREFIX + "PREVIEW_WORK_FILE_ACCESSIBILITY";
+
+            /**
+             * Label for tab and sidebar to indicate personal content.
+             */
+            public static final String PERSONAL_TAB = PREFIX + "PERSONAL_TAB";
+
+            /**
+             * Label for tab and sidebar tab to indicate work content
+             */
+            public static final String WORK_TAB = PREFIX + "WORK_TAB";
+
+            /**
+             * Accessibility label to indicate the subject(e.g. file/folder) is from work profile.
+             */
+            public static final String WORK_ACCESSIBILITY = PREFIX + "WORK_ACCESSIBILITY";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(WORK_PROFILE_OFF_ERROR_TITLE);
+                strings.add(WORK_PROFILE_OFF_ENABLE_BUTTON);
+                strings.add(CANT_SELECT_WORK_FILES_TITLE);
+                strings.add(CANT_SELECT_WORK_FILES_MESSAGE);
+                strings.add(CANT_SELECT_PERSONAL_FILES_TITLE);
+                strings.add(CANT_SELECT_PERSONAL_FILES_MESSAGE);
+                strings.add(CANT_SAVE_TO_WORK_TITLE);
+                strings.add(CANT_SAVE_TO_WORK_MESSAGE);
+                strings.add(CANT_SAVE_TO_PERSONAL_TITLE);
+                strings.add(CANT_SAVE_TO_PERSONAL_MESSAGE);
+                strings.add(CROSS_PROFILE_NOT_ALLOWED_TITLE);
+                strings.add(CROSS_PROFILE_NOT_ALLOWED_MESSAGE);
+                strings.add(PREVIEW_WORK_FILE_ACCESSIBILITY);
+                strings.add(PERSONAL_TAB);
+                strings.add(WORK_TAB);
+                strings.add(WORK_ACCESSIBILITY);
+                return strings;
+            }
+        }
+
+        /**
+         * Class containing the identifiers used to update device management-related system strings
+         * in the MediaProvider module.
+         */
+        public static final class MediaProvider {
+
+            private MediaProvider() {
+            }
+
+            private static final String PREFIX = "MediaProvider.";
+
+            /**
+             * The text shown to switch to the work profile in PhotoPicker.
+             */
+            public static final String SWITCH_TO_WORK_MESSAGE =
+                    PREFIX + "SWITCH_TO_WORK_MESSAGE";
+
+            /**
+             * The text shown to switch to the personal profile in PhotoPicker.
+             */
+            public static final String SWITCH_TO_PERSONAL_MESSAGE =
+                    PREFIX + "SWITCH_TO_PERSONAL_MESSAGE";
+
+            /**
+             * The title for error dialog in PhotoPicker when the admin blocks cross user
+             * interaction for the intent.
+             */
+            public static final String BLOCKED_BY_ADMIN_TITLE =
+                    PREFIX + "BLOCKED_BY_ADMIN_TITLE";
+
+            /**
+             * The message for error dialog in PhotoPicker when the admin blocks cross user
+             * interaction from the personal profile.
+             */
+            public static final String BLOCKED_FROM_PERSONAL_MESSAGE =
+                    PREFIX + "BLOCKED_FROM_PERSONAL_MESSAGE";
+
+            /**
+             * The message for error dialog in PhotoPicker when the admin blocks cross user
+             * interaction from the work profile.
+             */
+            public static final String BLOCKED_FROM_WORK_MESSAGE =
+                    PREFIX + "BLOCKED_FROM_WORK_MESSAGE";
+
+            /**
+             * The title of the error dialog in PhotoPicker when the user tries to switch to work
+             * content, but work profile is off.
+             */
+            public static final String WORK_PROFILE_PAUSED_TITLE =
+                    PREFIX + "WORK_PROFILE_PAUSED_TITLE";
+
+            /**
+             * The message of the error dialog in PhotoPicker when the user tries to switch to work
+             * content, but work profile is off.
+             */
+            public static final String WORK_PROFILE_PAUSED_MESSAGE =
+                    PREFIX + "WORK_PROFILE_PAUSED_MESSAGE";
+
+            /**
+             * @hide
+             */
+            static Set<String> buildStringsSet() {
+                Set<String> strings = new HashSet<>();
+                strings.add(SWITCH_TO_WORK_MESSAGE);
+                strings.add(SWITCH_TO_PERSONAL_MESSAGE);
+                strings.add(BLOCKED_BY_ADMIN_TITLE);
+                strings.add(BLOCKED_FROM_PERSONAL_MESSAGE);
+                strings.add(BLOCKED_FROM_WORK_MESSAGE);
+                strings.add(WORK_PROFILE_PAUSED_TITLE);
+                strings.add(WORK_PROFILE_PAUSED_MESSAGE);
+                return strings;
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyStringResource.aidl b/core/java/android/app/admin/DevicePolicyStringResource.aidl
new file mode 100644
index 0000000..13b0b95
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyStringResource.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable DevicePolicyStringResource;
diff --git a/core/java/android/app/admin/DevicePolicyStringResource.java b/core/java/android/app/admin/DevicePolicyStringResource.java
new file mode 100644
index 0000000..5f09bfd
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyStringResource.java
@@ -0,0 +1,145 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Used to pass in the required information for updating an enterprise string resource using
+ * {@link DevicePolicyManager#setStrings}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DevicePolicyStringResource implements Parcelable {
+    @NonNull private final @DevicePolicyResources.UpdatableStringId String mStringId;
+    private final @StringRes int mCallingPackageResourceId;
+    @NonNull private ParcelableResource mResource;
+
+    /**
+     * Creates an object containing the required information for updating an enterprise string
+     * resource using {@link DevicePolicyManager#setStrings}.
+     *
+     * <p>It will be used to update the string defined by {@code stringId} to the string with ID
+     * {@code callingPackageResourceId} in the calling package</p>
+     *
+     * @param stringId The ID of the string to update.
+     * @param callingPackageResourceId The ID of the {@link StringRes} in the calling package to
+     * use as an updated resource.
+     *
+     * @throws IllegalStateException if the resource with ID {@code callingPackageResourceId}
+     * doesn't exist in the {@code context} package.
+     */
+    public DevicePolicyStringResource(
+            @NonNull Context context,
+            @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
+            @StringRes int callingPackageResourceId) {
+        this(stringId, callingPackageResourceId, new ParcelableResource(
+                context, callingPackageResourceId, ParcelableResource.RESOURCE_TYPE_STRING));
+    }
+
+    private DevicePolicyStringResource(
+            @NonNull @DevicePolicyResources.UpdatableStringId String stringId,
+            @StringRes int callingPackageResourceId,
+            @NonNull ParcelableResource resource) {
+        Objects.requireNonNull(stringId, "stringId must be provided.");
+        Objects.requireNonNull(resource, "ParcelableResource must be provided.");
+
+        this.mStringId = stringId;
+        this.mCallingPackageResourceId = callingPackageResourceId;
+        this.mResource = resource;
+    }
+
+    /**
+     * Returns the ID of the string to update.
+     */
+    @DevicePolicyResources.UpdatableStringId
+    @NonNull
+    public String getStringId() {
+        return mStringId;
+    }
+
+    /**
+     * Returns the ID of the {@link StringRes} in the calling package to use as an updated
+     * resource.
+     */
+    public int getCallingPackageResourceId() {
+        return mCallingPackageResourceId;
+    }
+
+    /**
+     * Returns the {@link ParcelableResource} of the string.
+     *
+     * @hide
+     */
+    @NonNull
+    public ParcelableResource getResource() {
+        return mResource;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DevicePolicyStringResource other = (DevicePolicyStringResource) o;
+        return mStringId == other.mStringId
+                && mCallingPackageResourceId == other.mCallingPackageResourceId
+                && mResource.equals(other.mResource);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStringId, mCallingPackageResourceId, mResource);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mStringId);
+        dest.writeInt(mCallingPackageResourceId);
+        dest.writeTypedObject(mResource, flags);
+    }
+
+    public static final @NonNull Creator<DevicePolicyStringResource> CREATOR =
+            new Creator<DevicePolicyStringResource>() {
+        @Override
+        public DevicePolicyStringResource createFromParcel(Parcel in) {
+            String stringId = in.readString();
+            int callingPackageResourceId = in.readInt();
+            ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR);
+
+            return new DevicePolicyStringResource(stringId, callingPackageResourceId, resource);
+        }
+
+        @Override
+        public DevicePolicyStringResource[] newArray(int size) {
+            return new DevicePolicyStringResource[size];
+        }
+    };
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8320087..7525d54 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,11 +18,13 @@
 package android.app.admin;
 
 import android.app.admin.DevicePolicyDrawableResource;
+import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.ParcelableResource;
 import android.app.admin.NetworkEvent;
 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;
@@ -46,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;
 
@@ -118,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);
@@ -282,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);
@@ -532,8 +541,20 @@
     boolean isUsbDataSignalingEnabledForUser(int userId);
     boolean canUsbDataSignalingBeDisabled();
 
+    void setMinimumRequiredWifiSecurityLevel(int level);
+    int getMinimumRequiredWifiSecurityLevel();
+
+    void setSsidAllowlist(in List<String> ssids);
+    List<String> getSsidAllowlist();
+    void setSsidDenylist(in List<String> ssids);
+    List<String> getSsidDenylist();
+
     List<UserHandle> listForegroundAffiliatedUsers();
-    void setDrawables(in List<DevicePolicyDrawableResource> resource);
-    void resetDrawables(in int[] drawableIds);
-    ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource);
+    void setDrawables(in List<DevicePolicyDrawableResource> drawables);
+    void resetDrawables(in String[] drawableIds);
+    ParcelableResource getDrawable(String drawableId, String drawableStyle, String drawableSource);
+
+    void setStrings(in List<DevicePolicyStringResource> strings);
+    void resetStrings(in String[] stringIds);
+    ParcelableResource getString(String stringId);
 }
diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java
index e517162..dba3628 100644
--- a/core/java/android/app/admin/ParcelableResource.java
+++ b/core/java/android/app/admin/ParcelableResource.java
@@ -43,7 +43,7 @@
 
 /**
  * Used to store the required information to load a resource that was updated using
- * {@link DevicePolicyManager#setDrawables}.
+ * {@link DevicePolicyManager#setDrawables} and {@link DevicePolicyManager#setStrings}.
  *
  * @hide
  */
@@ -57,10 +57,13 @@
     private static final String ATTR_RESOURCE_TYPE = "resource-type";
 
     public static final int RESOURCE_TYPE_DRAWABLE = 1;
+    public static final int RESOURCE_TYPE_STRING = 2;
+
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "RESOURCE_TYPE_" }, value = {
-            RESOURCE_TYPE_DRAWABLE
+            RESOURCE_TYPE_DRAWABLE,
+            RESOURCE_TYPE_STRING
     })
     public @interface ResourceType {}
 
@@ -78,13 +81,11 @@
      *                resource
      * @param resourceId of the resource to use as an updated resource
      * @param resourceType see {@link ResourceType}
-     * @throws IllegalArgumentException if the given {@code resourceId} doesn't exist in the
-     * {@link Context#getResources()} of the given {@code context}
      */
-    public ParcelableResource(@NonNull Context context, @AnyRes int resourceId,
-            @ResourceType int resourceType) throws IllegalArgumentException {
+    public ParcelableResource(
+            @NonNull Context context, @AnyRes int resourceId, @ResourceType int resourceType)
+            throws IllegalStateException, IllegalArgumentException {
         Objects.requireNonNull(context, "context must be provided");
-
         verifyResourceExistsInCallingPackage(context, resourceId, resourceType);
 
         this.mResourceId = resourceId;
@@ -108,25 +109,41 @@
 
     private static void verifyResourceExistsInCallingPackage(
             Context context, @AnyRes int resourceId, @ResourceType int resourceType)
-            throws IllegalArgumentException {
+            throws IllegalStateException, IllegalArgumentException {
         switch (resourceType) {
             case RESOURCE_TYPE_DRAWABLE:
                 if (!hasDrawableInCallingPackage(context, resourceId)) {
-                    throw new IllegalArgumentException(String.format(
+                    throw new IllegalStateException(String.format(
                             "Drawable with id %d doesn't exist in the calling package %s",
                             resourceId,
                             context.getPackageName()));
                 }
                 break;
+            case RESOURCE_TYPE_STRING:
+                if (!hasStringInCallingPackage(context, resourceId)) {
+                    throw new IllegalStateException(String.format(
+                            "String with id %d doesn't exist in the calling package %s",
+                            resourceId,
+                            context.getPackageName()));
+                }
+                break;
             default:
                 throw new IllegalArgumentException(
-                        "Unknown ParcelableDevicePolicyResourceType: " + resourceType);
+                        "Unknown ResourceType: " + resourceType);
         }
     }
 
     private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) {
         try {
-            return context.getDrawable(resourceId) != null;
+            return "drawable".equals(context.getResources().getResourceTypeName(resourceId));
+        } catch (Resources.NotFoundException e) {
+            return false;
+        }
+    }
+
+    private static boolean hasStringInCallingPackage(Context context, @AnyRes int resourceId) {
+        try {
+            return "string".equals(context.getResources().getResourceTypeName(resourceId));
         } catch (Resources.NotFoundException e) {
             return false;
         }
@@ -158,7 +175,7 @@
      * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
      * drawable was not found or could not be loaded.</p>
      */
-    @Nullable
+    @NonNull
     public Drawable getDrawable(
             Context context,
             int density,
@@ -175,6 +192,59 @@
         }
     }
 
+    /**
+     * Loads the string with id {@code mResourceId} from {@code mPackageName} using the
+     * configuration returned from {@link Resources#getConfiguration} of the provided
+     * {@code context}.
+     *
+     * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
+     * string was not found or could not be loaded.</p>
+     */
+    @NonNull
+    public String getString(
+            Context context,
+            @NonNull Callable<String> defaultStringLoader) {
+        // TODO(b/203548565): properly handle edge case when the device manager role holder is
+        //  unavailable because it's being updated.
+        try {
+            Resources resources = getAppResourcesWithCallersConfiguration(context);
+            verifyResourceName(resources);
+            return resources.getString(mResourceId);
+        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
+            Slog.e(TAG, "Unable to load string resource " + mResourceName, e);
+            return loadDefaultString(defaultStringLoader);
+        }
+    }
+
+    /**
+     * Loads the string with id {@code mResourceId} from {@code mPackageName} using the
+     * configuration returned from {@link Resources#getConfiguration} of the provided
+     * {@code context}.
+     *
+     * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
+     * string was not found or could not be loaded.</p>
+     */
+    @Nullable
+    public String getString(
+            Context context,
+            @NonNull Callable<String> defaultStringLoader,
+            @NonNull Object... formatArgs) {
+        // TODO(b/203548565): properly handle edge case when the device manager role holder is
+        //  unavailable because it's being updated.
+        try {
+            Resources resources = getAppResourcesWithCallersConfiguration(context);
+            verifyResourceName(resources);
+            String rawString = resources.getString(mResourceId);
+            return String.format(
+                    context.getResources().getConfiguration().getLocales().get(0),
+                    rawString,
+                    formatArgs);
+        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
+            Slog.e(TAG, "Unable to load string resource " + mResourceName, e);
+            return loadDefaultString(defaultStringLoader);
+        }
+    }
+
     private Resources getAppResourcesWithCallersConfiguration(Context context)
             throws PackageManager.NameNotFoundException {
         PackageManager pm = context.getPackageManager();
@@ -195,15 +265,40 @@
     }
 
     /**
-     * returns the {@link Drawable} loaded from calling
-     * {@code defaultDrawableLoader}.
+     * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
      */
-    public static Drawable loadDefaultDrawable(
-            @NonNull Callable<Drawable> defaultDrawableLoader) {
+    @NonNull
+    public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) {
         try {
-            return defaultDrawableLoader.call();
+            Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
+
+            Drawable drawable = defaultDrawableLoader.call();
+            Objects.requireNonNull(drawable, "defaultDrawable can't be null");
+
+            return drawable;
+        } catch (NullPointerException rethrown) {
+            throw rethrown;
         } catch (Exception e) {
-            throw new RuntimeException("Couldn't load default drawable", e);
+            throw new RuntimeException("Couldn't load default drawable: ", e);
+        }
+    }
+
+    /**
+     * returns the {@link String} loaded from calling {@code defaultStringLoader}.
+     */
+    @NonNull
+    public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) {
+        try {
+            Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
+
+            String string = defaultStringLoader.call();
+            Objects.requireNonNull(string, "defaultString can't be null");
+
+            return string;
+        } catch (NullPointerException rethrown) {
+            throw rethrown;
+        } catch (Exception e) {
+            throw new RuntimeException("Couldn't load default string: ", e);
         }
     }
 
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/app/admin/WifiSsidPolicy.java b/core/java/android/app/admin/WifiSsidPolicy.java
new file mode 100644
index 0000000..3715017
--- /dev/null
+++ b/core/java/android/app/admin/WifiSsidPolicy.java
@@ -0,0 +1,153 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Used to indicate the Wi-Fi SSID restriction policy the network must satisfy
+ * in order to be eligible for a connection.
+ *
+ * If the policy type is a denylist, the device may not connect to networks on the denylist.
+ * If the policy type is an allowlist, the device may only connect to networks on the allowlist.
+ * Admin configured networks are not exempt from this restriction.
+ * This policy only prohibits connecting to a restricted network and
+ * does not affect adding a restricted network.
+ * If the current network is present in the denylist or not present in the allowlist,
+ * it will be disconnected.
+ */
+public final class WifiSsidPolicy implements Parcelable {
+    /**
+     * SSID policy type indicator for {@link WifiSsidPolicy}.
+     *
+     * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant
+     * indicates that the SSID policy type is an allowlist.
+     *
+     * @see #WIFI_SSID_POLICY_TYPE_DENYLIST
+     */
+    public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0;
+
+    /**
+     * SSID policy type indicator for {@link WifiSsidPolicy}.
+     *
+     * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant
+     * indicates that the SSID policy type is a denylist.
+     *
+     * @see #WIFI_SSID_POLICY_TYPE_ALLOWLIST
+     */
+    public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1;
+
+    /**
+     * Possible SSID policy types
+     *
+     * @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_SSID_POLICY_TYPE_"}, value = {
+            WIFI_SSID_POLICY_TYPE_ALLOWLIST,
+            WIFI_SSID_POLICY_TYPE_DENYLIST})
+    public @interface WifiSsidPolicyType {}
+
+    private @WifiSsidPolicyType int mPolicyType;
+    private ArraySet<String> mSsids;
+
+    private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<String> ssids) {
+        mPolicyType = policyType;
+        mSsids = new ArraySet<>(ssids);
+    }
+
+    private WifiSsidPolicy(Parcel in) {
+        mPolicyType = in.readInt();
+        mSsids = (ArraySet<String>) in.readArraySet(null);
+    }
+    /**
+     * Create the allowlist Wi-Fi SSID Policy.
+     *
+     * @param ssids allowlist of SSIDs in UTF-8 without double quotes format
+     * @throws IllegalArgumentException if the input ssids list is empty
+     */
+    @NonNull
+    public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<String> ssids) {
+        if (ssids.isEmpty()) {
+            throw new IllegalArgumentException("SSID list cannot be empty");
+        }
+        return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_ALLOWLIST, ssids);
+    }
+
+    /**
+     * Create the denylist Wi-Fi SSID Policy.
+     *
+     * @param ssids denylist of SSIDs in UTF-8 without double quotes format
+     * @throws IllegalArgumentException if the input ssids list is empty
+     */
+    @NonNull
+    public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<String> ssids) {
+        if (ssids.isEmpty()) {
+            throw new IllegalArgumentException("SSID list cannot be empty");
+        }
+        return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_DENYLIST, ssids);
+    }
+
+    /**
+     * Returns the set of SSIDs in UTF-8 without double quotes format.
+     */
+    @NonNull
+    public Set<String> getSsids() {
+        return mSsids;
+    }
+
+    /**
+     * Returns the policy type.
+     */
+    public @WifiSsidPolicyType int getPolicyType() {
+        return mPolicyType;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<WifiSsidPolicy> CREATOR = new Creator<WifiSsidPolicy>() {
+        @Override
+        public WifiSsidPolicy createFromParcel(Parcel source) {
+            return new WifiSsidPolicy(source);
+        }
+
+        @Override
+        public WifiSsidPolicy[] newArray(int size) {
+            return new WifiSsidPolicy[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPolicyType);
+        dest.writeArraySet(mSsids);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/app/ambientcontext/AmbientContextEvent.aidl
index 2b3e961..0965b1a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.app.ambientcontext;
 
-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
+parcelable AmbientContextEvent;
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
new file mode 100644
index 0000000..11e695ad
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -0,0 +1,492 @@
+/*
+ * 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.ambientcontext;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+
+
+/**
+ * Represents a detected ambient event. Each event has a type, start time, end time,
+ * plus some optional data.
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(
+        genBuilder = true,
+        genConstructor = false,
+        genHiddenConstDefs = true,
+        genParcelable = true,
+        genToString = true
+)
+public final class AmbientContextEvent implements Parcelable {
+    /**
+     * The integer indicating an unknown event was detected.
+     */
+    public static final int EVENT_UNKNOWN = 0;
+
+    /**
+     * The integer indicating a cough event was detected.
+     */
+    public static final int EVENT_COUGH = 1;
+
+    /**
+     * The integer indicating a snore event was detected.
+     */
+    public static final int EVENT_SNORE = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "EVENT_" }, value = {
+            EVENT_UNKNOWN,
+            EVENT_COUGH,
+            EVENT_SNORE,
+    }) public @interface EventCode {}
+
+    /** The integer indicating an unknown level. */
+    public static final int LEVEL_UNKNOWN = 0;
+
+    /** The integer indicating a low level. */
+    public static final int LEVEL_LOW = 1;
+
+    /** The integer indicating a medium low level. */
+    public static final int LEVEL_MEDIUM_LOW = 2;
+
+    /** The integer indicating a medium Level. */
+    public static final int LEVEL_MEDIUM = 3;
+
+    /** The integer indicating a medium high level. */
+    public static final int LEVEL_MEDIUM_HIGH = 4;
+
+    /** The integer indicating a high level. */
+    public static final int LEVEL_HIGH = 5;
+
+    /** @hide */
+    @IntDef(prefix = {"LEVEL_"}, value = {
+            LEVEL_UNKNOWN,
+            LEVEL_LOW,
+            LEVEL_MEDIUM_LOW,
+            LEVEL_MEDIUM,
+            LEVEL_MEDIUM_HIGH,
+            LEVEL_HIGH
+    }) public @interface LevelValue {}
+
+    @EventCode private final int mEventType;
+    private static int defaultEventType() {
+        return EVENT_UNKNOWN;
+    }
+
+    /** Event start time */
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class)
+    @NonNull private final Instant mStartTime;
+    @NonNull private static Instant defaultStartTime() {
+        return Instant.MIN;
+    }
+
+    /** Event end time */
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class)
+    @NonNull private final Instant mEndTime;
+    @NonNull private static Instant defaultEndTime() {
+        return Instant.MAX;
+    }
+
+    /**
+     * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+     * Apps can add post-processing filter using this value if needed.
+     */
+    @LevelValue private final int mConfidenceLevel;
+    private static int defaultConfidenceLevel() {
+        return LEVEL_UNKNOWN;
+    }
+
+    /**
+     * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+     * Apps can add post-processing filter using this value if needed.
+     */
+    @LevelValue private final int mDensityLevel;
+    private static int defaultDensityLevel() {
+        return LEVEL_UNKNOWN;
+    }
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "EVENT_", value = {
+        EVENT_UNKNOWN,
+        EVENT_COUGH,
+        EVENT_SNORE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Event {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String eventToString(@Event int value) {
+        switch (value) {
+            case EVENT_UNKNOWN:
+                    return "EVENT_UNKNOWN";
+            case EVENT_COUGH:
+                    return "EVENT_COUGH";
+            case EVENT_SNORE:
+                    return "EVENT_SNORE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /** @hide */
+    @IntDef(prefix = "LEVEL_", value = {
+        LEVEL_UNKNOWN,
+        LEVEL_LOW,
+        LEVEL_MEDIUM_LOW,
+        LEVEL_MEDIUM,
+        LEVEL_MEDIUM_HIGH,
+        LEVEL_HIGH
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Level {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String levelToString(@Level int value) {
+        switch (value) {
+            case LEVEL_UNKNOWN:
+                    return "LEVEL_UNKNOWN";
+            case LEVEL_LOW:
+                    return "LEVEL_LOW";
+            case LEVEL_MEDIUM_LOW:
+                    return "LEVEL_MEDIUM_LOW";
+            case LEVEL_MEDIUM:
+                    return "LEVEL_MEDIUM";
+            case LEVEL_MEDIUM_HIGH:
+                    return "LEVEL_MEDIUM_HIGH";
+            case LEVEL_HIGH:
+                    return "LEVEL_HIGH";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ AmbientContextEvent(
+            @EventCode int eventType,
+            @NonNull Instant startTime,
+            @NonNull Instant endTime,
+            @LevelValue int confidenceLevel,
+            @LevelValue int densityLevel) {
+        this.mEventType = eventType;
+        com.android.internal.util.AnnotationValidations.validate(
+                EventCode.class, null, mEventType);
+        this.mStartTime = startTime;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mStartTime);
+        this.mEndTime = endTime;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mEndTime);
+        this.mConfidenceLevel = confidenceLevel;
+        com.android.internal.util.AnnotationValidations.validate(
+                LevelValue.class, null, mConfidenceLevel);
+        this.mDensityLevel = densityLevel;
+        com.android.internal.util.AnnotationValidations.validate(
+                LevelValue.class, null, mDensityLevel);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @EventCode int getEventType() {
+        return mEventType;
+    }
+
+    /**
+     * Event start time
+     */
+    @DataClass.Generated.Member
+    public @NonNull Instant getStartTime() {
+        return mStartTime;
+    }
+
+    /**
+     * Event end time
+     */
+    @DataClass.Generated.Member
+    public @NonNull Instant getEndTime() {
+        return mEndTime;
+    }
+
+    /**
+     * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+     * Apps can add post-processing filter using this value if needed.
+     */
+    @DataClass.Generated.Member
+    public @LevelValue int getConfidenceLevel() {
+        return mConfidenceLevel;
+    }
+
+    /**
+     * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+     * Apps can add post-processing filter using this value if needed.
+     */
+    @DataClass.Generated.Member
+    public @LevelValue int getDensityLevel() {
+        return mDensityLevel;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "AmbientContextEvent { " +
+                "eventType = " + mEventType + ", " +
+                "startTime = " + mStartTime + ", " +
+                "endTime = " + mEndTime + ", " +
+                "confidenceLevel = " + mConfidenceLevel + ", " +
+                "densityLevel = " + mDensityLevel +
+        " }";
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<Instant> sParcellingForStartTime =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInstant.class);
+    static {
+        if (sParcellingForStartTime == null) {
+            sParcellingForStartTime = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInstant());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<Instant> sParcellingForEndTime =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInstant.class);
+    static {
+        if (sParcellingForEndTime == null) {
+            sParcellingForEndTime = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInstant());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mEventType);
+        sParcellingForStartTime.parcel(mStartTime, dest, flags);
+        sParcellingForEndTime.parcel(mEndTime, dest, flags);
+        dest.writeInt(mConfidenceLevel);
+        dest.writeInt(mDensityLevel);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ AmbientContextEvent(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int eventType = in.readInt();
+        Instant startTime = sParcellingForStartTime.unparcel(in);
+        Instant endTime = sParcellingForEndTime.unparcel(in);
+        int confidenceLevel = in.readInt();
+        int densityLevel = in.readInt();
+
+        this.mEventType = eventType;
+        com.android.internal.util.AnnotationValidations.validate(
+                EventCode.class, null, mEventType);
+        this.mStartTime = startTime;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mStartTime);
+        this.mEndTime = endTime;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mEndTime);
+        this.mConfidenceLevel = confidenceLevel;
+        com.android.internal.util.AnnotationValidations.validate(
+                LevelValue.class, null, mConfidenceLevel);
+        this.mDensityLevel = densityLevel;
+        com.android.internal.util.AnnotationValidations.validate(
+                LevelValue.class, null, mDensityLevel);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<AmbientContextEvent> CREATOR
+            = new Parcelable.Creator<AmbientContextEvent>() {
+        @Override
+        public AmbientContextEvent[] newArray(int size) {
+            return new AmbientContextEvent[size];
+        }
+
+        @Override
+        public AmbientContextEvent createFromParcel(@NonNull android.os.Parcel in) {
+            return new AmbientContextEvent(in);
+        }
+    };
+
+    /**
+     * A builder for {@link AmbientContextEvent}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @EventCode int mEventType;
+        private @NonNull Instant mStartTime;
+        private @NonNull Instant mEndTime;
+        private @LevelValue int mConfidenceLevel;
+        private @LevelValue int mDensityLevel;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setEventType(@EventCode int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mEventType = value;
+            return this;
+        }
+
+        /**
+         * Event start time
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setStartTime(@NonNull Instant value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mStartTime = value;
+            return this;
+        }
+
+        /**
+         * Event end time
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setEndTime(@NonNull Instant value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mEndTime = value;
+            return this;
+        }
+
+        /**
+         * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+         * Apps can add post-processing filter using this value if needed.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setConfidenceLevel(@LevelValue int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mConfidenceLevel = value;
+            return this;
+        }
+
+        /**
+         * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available.
+         * Apps can add post-processing filter using this value if needed.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setDensityLevel(@LevelValue int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10;
+            mDensityLevel = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull AmbientContextEvent build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mEventType = defaultEventType();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mStartTime = defaultStartTime();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mEndTime = defaultEndTime();
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mConfidenceLevel = defaultConfidenceLevel();
+            }
+            if ((mBuilderFieldsSet & 0x10) == 0) {
+                mDensityLevel = defaultDensityLevel();
+            }
+            AmbientContextEvent o = new AmbientContextEvent(
+                    mEventType,
+                    mStartTime,
+                    mEndTime,
+                    mConfidenceLevel,
+                    mDensityLevel);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x20) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1642040319323L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
+            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl
new file mode 100644
index 0000000..e24c6ad
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ambientcontext;
+
+parcelable AmbientContextEventRequest;
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java
new file mode 100644
index 0000000..82b16a2
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.ambientcontext;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.util.ArraySet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents the request for ambient event detection.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AmbientContextEventRequest implements Parcelable {
+    @NonNull private final Set<Integer> mEventTypes;
+    @NonNull private final PersistableBundle mOptions;
+
+    AmbientContextEventRequest(
+            @NonNull Set<Integer> eventTypes,
+            @NonNull PersistableBundle options) {
+        this.mEventTypes = eventTypes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mEventTypes);
+        this.mOptions = options;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mOptions);
+    }
+
+    /**
+     * The event types to detect.
+     */
+    public @NonNull Set<Integer> getEventTypes() {
+        return mEventTypes;
+    }
+
+    /**
+     * Optional detection options.
+     */
+    public @NonNull PersistableBundle getOptions() {
+        return mOptions;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientContextEventRequest { " + "eventTypes = " + mEventTypes + ", "
+                + "options = " + mOptions + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeArraySet(new ArraySet<>(mEventTypes));
+        dest.writeTypedObject(mOptions, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    AmbientContextEventRequest(@NonNull Parcel in) {
+        Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader());
+        PersistableBundle options = (PersistableBundle) in.readTypedObject(
+                PersistableBundle.CREATOR);
+
+        this.mEventTypes = eventTypes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mEventTypes);
+        this.mOptions = options;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mOptions);
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientContextEventRequest> CREATOR =
+            new Parcelable.Creator<AmbientContextEventRequest>() {
+        @Override
+        public AmbientContextEventRequest[] newArray(int size) {
+            return new AmbientContextEventRequest[size];
+        }
+
+        @Override
+        public AmbientContextEventRequest createFromParcel(@NonNull Parcel in) {
+            return new AmbientContextEventRequest(in);
+        }
+    };
+
+    /**
+     * A builder for {@link AmbientContextEventRequest}
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+        private @NonNull Set<Integer> mEventTypes;
+        private @NonNull PersistableBundle mOptions;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Add an event type to detect.
+         */
+        public @NonNull Builder addEventType(@AmbientContextEvent.EventCode int value) {
+            checkNotUsed();
+            if (mEventTypes == null) {
+                mBuilderFieldsSet |= 0x1;
+                mEventTypes = new HashSet<>();
+            }
+            mEventTypes.add(value);
+            return this;
+        }
+
+        /**
+         * Optional detection options.
+         */
+        public @NonNull Builder setOptions(@NonNull PersistableBundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mOptions = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull AmbientContextEventRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mEventTypes = new HashSet<>();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mOptions = new PersistableBundle();
+            }
+            AmbientContextEventRequest o = new AmbientContextEventRequest(
+                    mEventTypes,
+                    mOptions);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
index 2b3e961..4dc6466 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.app.ambientcontext;
 
-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
+parcelable AmbientContextEventResponse;
\ No newline at end of file
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java
new file mode 100644
index 0000000..472a78b
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java
@@ -0,0 +1,293 @@
+/*
+ * 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.ambientcontext;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a response from the {@code AmbientContextEvent} service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AmbientContextEventResponse implements Parcelable {
+    /**
+     * An unknown status.
+     */
+    public static final int STATUS_UNKNOWN = 0;
+    /**
+     * The value of the status code that indicates success.
+     */
+    public static final int STATUS_SUCCESS = 1;
+    /**
+     * The value of the status code that indicates one or more of the
+     * requested events are not supported.
+     */
+    public static final int STATUS_NOT_SUPPORTED = 2;
+    /**
+     * The value of the status code that indicates service not available.
+     */
+    public static final int STATUS_SERVICE_UNAVAILABLE = 3;
+    /**
+     * The value of the status code that microphone is disabled.
+     */
+    public static final int STATUS_MICROPHONE_DISABLED = 4;
+    /**
+     * The value of the status code that the app is not granted access.
+     */
+    public static final int STATUS_ACCESS_DENIED = 5;
+
+    /** @hide */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_SUCCESS,
+            STATUS_NOT_SUPPORTED,
+            STATUS_SERVICE_UNAVAILABLE,
+            STATUS_MICROPHONE_DISABLED,
+            STATUS_ACCESS_DENIED
+    }) public @interface StatusCode {}
+
+    @StatusCode private final int mStatusCode;
+    @NonNull private final List<AmbientContextEvent> mEvents;
+    @NonNull private final String mPackageName;
+    @Nullable private final PendingIntent mActionPendingIntent;
+
+    /** @hide */
+    public static String statusToString(@StatusCode int value) {
+        switch (value) {
+            case STATUS_UNKNOWN:
+                return "STATUS_UNKNOWN";
+            case STATUS_SUCCESS:
+                return "STATUS_SUCCESS";
+            case STATUS_NOT_SUPPORTED:
+                return "STATUS_NOT_SUPPORTED";
+            case STATUS_SERVICE_UNAVAILABLE:
+                return "STATUS_SERVICE_UNAVAILABLE";
+            case STATUS_MICROPHONE_DISABLED:
+                return "STATUS_MICROPHONE_DISABLED";
+            case STATUS_ACCESS_DENIED:
+                return "STATUS_ACCESS_DENIED";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    AmbientContextEventResponse(
+            @StatusCode int statusCode,
+            @NonNull List<AmbientContextEvent> events,
+            @NonNull String packageName,
+            @Nullable PendingIntent actionPendingIntent) {
+        this.mStatusCode = statusCode;
+        AnnotationValidations.validate(StatusCode.class, null, mStatusCode);
+        this.mEvents = events;
+        AnnotationValidations.validate(NonNull.class, null, mEvents);
+        this.mPackageName = packageName;
+        AnnotationValidations.validate(NonNull.class, null, mPackageName);
+        this.mActionPendingIntent = actionPendingIntent;
+    }
+
+    /**
+     * The status of the response.
+     */
+    public @StatusCode int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /**
+     * The detected event.
+     */
+    public @NonNull List<AmbientContextEvent> getEvents() {
+        return mEvents;
+    }
+
+    /**
+     * The package to deliver the response to.
+     */
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * A {@link PendingIntent} that the client should call to allow further actions by user.
+     * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the
+     * grant access activity.
+     */
+    public @Nullable PendingIntent getActionPendingIntent() {
+        return mActionPendingIntent;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", "
+                + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", "
+                + "callbackPendingIntent = " + mActionPendingIntent + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        byte flg = 0;
+        if (mActionPendingIntent != null) flg |= 0x8;
+        dest.writeByte(flg);
+        dest.writeInt(mStatusCode);
+        dest.writeParcelableList(mEvents, flags);
+        dest.writeString(mPackageName);
+        if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    AmbientContextEventResponse(@NonNull android.os.Parcel in) {
+        byte flg = in.readByte();
+        int statusCode = in.readInt();
+        List<AmbientContextEvent> events = new ArrayList<>();
+        in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(),
+                AmbientContextEvent.class);
+        String packageName = in.readString();
+        PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null
+                : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR);
+
+        this.mStatusCode = statusCode;
+        AnnotationValidations.validate(
+                StatusCode.class, null, mStatusCode);
+        this.mEvents = events;
+        AnnotationValidations.validate(
+                NonNull.class, null, mEvents);
+        this.mPackageName = packageName;
+        AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mActionPendingIntent = callbackPendingIntent;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR =
+            new Parcelable.Creator<AmbientContextEventResponse>() {
+        @Override
+        public AmbientContextEventResponse[] newArray(int size) {
+            return new AmbientContextEventResponse[size];
+        }
+
+        @Override
+        public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) {
+            return new AmbientContextEventResponse(in);
+        }
+    };
+
+    /**
+     * A builder for {@link AmbientContextEventResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+        private @StatusCode int mStatusCode;
+        private @NonNull List<AmbientContextEvent> mEvents;
+        private @NonNull String mPackageName;
+        private @Nullable PendingIntent mCallbackPendingIntent;
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * The status of the response.
+         */
+        public @NonNull Builder setStatusCode(@StatusCode int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mStatusCode = value;
+            return this;
+        }
+
+        /**
+         * Adds an event to the builder.
+         */
+        public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) {
+            checkNotUsed();
+            if (mEvents == null) {
+                mBuilderFieldsSet |= 0x2;
+                mEvents = new ArrayList<>();
+            }
+            mEvents.add(value);
+            return this;
+        }
+
+        /**
+         * The package to deliver the response to.
+         */
+        public @NonNull Builder setPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mPackageName = value;
+            return this;
+        }
+
+        /**
+         * A {@link PendingIntent} that the client should call to allow further actions by user.
+         * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to
+         * the grant access activity.
+         */
+        public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mCallbackPendingIntent = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull AmbientContextEventResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mStatusCode = STATUS_UNKNOWN;
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mEvents = new ArrayList<>();
+            }
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mPackageName = "";
+            }
+            if ((mBuilderFieldsSet & 0x8) == 0) {
+                mCallbackPendingIntent = null;
+            }
+            AmbientContextEventResponse o = new AmbientContextEventResponse(
+                    mStatusCode,
+                    mEvents,
+                    mPackageName,
+                    mCallbackPendingIntent);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
new file mode 100644
index 0000000..6841d1b
--- /dev/null
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -0,0 +1,147 @@
+/*
+ * 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.ambientcontext;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s.
+ * After successful registration, the app receives a callback on the provided {@link PendingIntent}
+ * when the requested event is detected.
+ * <p />
+ *
+ * Example:
+ *
+ * <pre><code>
+ *     // Create request
+ *     AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
+ *         .addEventType(AmbientContextEvent.EVENT_COUGH)
+ *         .addEventTYpe(AmbientContextEvent.EVENT_SNORE)
+ *         .build();
+ *     // Create PendingIntent
+ *     Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
+ *         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ *     PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0);
+ *     // Register for events
+ *     AmbientContextManager ambientContextManager =
+ *         context.getSystemService(AmbientContextManager.class);
+ *    ambientContextManager.registerObserver(request, pendingIntent);
+ *
+ *    // Handle the callback intent in your receiver
+ *    {@literal @}Override
+ *    protected void onReceive(Context context, Intent intent) {
+ *      AmbientContextEventResponse response =
+ *          AmbientContextManager.getResponseFromIntent(intent);
+ *      if (response != null) {
+ *        if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) {
+ *          // Do something useful with response.getEvent()
+ *        } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) {
+ *          // Redirect users to grant access
+ *          PendingIntent callbackPendingIntent = response.getCallbackPendingIntent();
+ *          if (callbackPendingIntent != null) {
+ *            callbackPendingIntent.send();
+ *          }
+ *        } else ...
+ *      }
+ *    }
+ * </code></pre>
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.AMBIENT_CONTEXT_SERVICE)
+public final class AmbientContextManager {
+
+    /**
+     * The key of an Intent extra indicating the response.
+     */
+    public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE =
+            "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
+
+    /**
+     * Allows clients to retrieve the response from the intent.
+     * @param intent received from the PendingIntent callback
+     *
+     * @return the AmbientContextEventResponse, or null if not present
+     */
+    @Nullable
+    public static AmbientContextEventResponse getResponseFromIntent(
+            @NonNull Intent intent) {
+        if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) {
+            return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE);
+        } else {
+            return null;
+        }
+    }
+
+    private final Context mContext;
+    private final IAmbientContextEventObserver mService;
+
+    /**
+     * {@hide}
+     */
+    public AmbientContextManager(Context context, IAmbientContextEventObserver service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Allows app to register as a {@link AmbientContextEvent} observer. The
+     * observer receives a callback on the provided {@link PendingIntent} when the requested
+     * event is detected. Registering another observer from the same package that has already been
+     * registered will override the previous observer.
+     *
+     * @param request The request with events to observe.
+     * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any
+     *                     requested event is detected.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
+    public void registerObserver(
+            @NonNull AmbientContextEventRequest request,
+            @NonNull PendingIntent pendingIntent) {
+        Preconditions.checkArgument(!pendingIntent.isImmutable());
+        try {
+            mService.registerObserver(request, pendingIntent);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an
+     * observer that was already unregistered or never registered will have no effect.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
+    public void unregisterObserver() {
+        try {
+            mService.unregisterObserver(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl
new file mode 100644
index 0000000..9032fe1
--- /dev/null
+++ b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.app.ambientcontext;
+
+import android.app.PendingIntent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+
+/**
+ * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents.
+ *
+ * @hide
+ */
+oneway interface IAmbientContextEventObserver {
+    void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent);
+    void unregisterObserver(in String callingPackage);
+}
\ No newline at end of file
diff --git a/core/java/android/app/ambientcontext/OWNERS b/core/java/android/app/ambientcontext/OWNERS
new file mode 100644
index 0000000..a863297
--- /dev/null
+++ b/core/java/android/app/ambientcontext/OWNERS
@@ -0,0 +1,3 @@
+enxun@google.com
+kxchen@google.com
+tgadh@google.com
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index e1f6af0..6e49e95 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -815,13 +815,13 @@
                     mAutofillHints = in.readStringArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-                    mAutofillValue = in.readParcelable(null);
+                    mAutofillValue = in.readParcelable(null, android.view.autofill.AutofillValue.class);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
                     mAutofillOptions = in.readCharSequenceArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) {
-                    mHtmlInfo = in.readParcelable(null);
+                    mHtmlInfo = in.readParcelable(null, android.view.ViewStructure.HtmlInfo.class);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) {
                     mMinEms = in.readInt();
@@ -886,7 +886,7 @@
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
-                mLocaleList = in.readParcelable(null);
+                mLocaleList = in.readParcelable(null, android.os.LocaleList.class);
             }
             if ((flags & FLAGS_HAS_MIME_TYPES) != 0) {
                 mReceiveContentMimeTypes = in.readStringArray();
diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
index 2bf71b0..ab350f2 100644
--- a/core/java/android/app/people/ConversationChannel.java
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -83,16 +83,16 @@
     }
 
     public ConversationChannel(Parcel in) {
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
         mUid = in.readInt();
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mNotificationChannelGroup =
-                in.readParcelable(NotificationChannelGroup.class.getClassLoader());
+                in.readParcelable(NotificationChannelGroup.class.getClassLoader(), android.app.NotificationChannelGroup.class);
         mLastEventTimestamp = in.readLong();
         mHasActiveNotifications = in.readBoolean();
         mHasBirthdayToday = in.readBoolean();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
     }
 
     @Override
diff --git a/core/java/android/app/people/ConversationStatus.java b/core/java/android/app/people/ConversationStatus.java
index 8038158..a7b61b3 100644
--- a/core/java/android/app/people/ConversationStatus.java
+++ b/core/java/android/app/people/ConversationStatus.java
@@ -126,7 +126,7 @@
         mActivity = p.readInt();
         mAvailability = p.readInt();
         mDescription = p.readCharSequence();
-        mIcon = p.readParcelable(Icon.class.getClassLoader());
+        mIcon = p.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
         mStartTimeMs = p.readLong();
         mEndTimeMs = p.readLong();
     }
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index e11861f..4337111 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -472,9 +472,9 @@
     public PeopleSpaceTile(Parcel in) {
         mId = in.readString();
         mUserName = in.readCharSequence();
-        mUserIcon = in.readParcelable(Icon.class.getClassLoader());
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
-        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader());
+        mUserIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
         mPackageName = in.readString();
         mBirthdayText = in.readString();
         mLastInteractionTimestamp = in.readLong();
@@ -483,12 +483,12 @@
         mNotificationContent = in.readCharSequence();
         mNotificationSender = in.readCharSequence();
         mNotificationCategory = in.readString();
-        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
+        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mMessagesCount = in.readInt();
-        mIntent = in.readParcelable(Intent.class.getClassLoader());
+        mIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
         mNotificationTimestamp = in.readLong();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
         mCanBypassDnd = in.readBoolean();
         mIsPackageSuspended = in.readBoolean();
         mIsUserQuieted = in.readBoolean();
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 963e750..51e3953 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -72,7 +72,7 @@
     }
 
     private AppTargetEvent(Parcel parcel) {
-        mTarget = parcel.readParcelable(null);
+        mTarget = parcel.readParcelable(null, android.app.prediction.AppTarget.class);
         mLocation = parcel.readString();
         mAction = parcel.readInt();
     }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index fbb37db..30a6c31 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -197,11 +197,11 @@
         if (readActivityToken) {
             mActivityToken = in.readStrongBinder();
         }
-        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
         final boolean readActivityCallbacks = in.readBoolean();
         if (readActivityCallbacks) {
             mActivityCallbacks = new ArrayList<>();
-            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
         }
     }
 
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 61f8723..89caab7 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -96,7 +96,7 @@
     }
 
     private SmartspaceTargetEvent(Parcel parcel) {
-        mSmartspaceTarget = parcel.readParcelable(null);
+        mSmartspaceTarget = parcel.readParcelable(null, android.app.smartspace.SmartspaceTarget.class);
         mSmartspaceActionId = parcel.readString();
         mEventType = parcel.readInt();
     }
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index 8e281c0..0f98b44 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -101,11 +101,11 @@
     }
 
     private static ExternalTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         ExternalTimeSuggestion suggestion =
                 new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue());
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index 4a10447..71fce14 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -59,8 +59,8 @@
 
     @NonNull
     private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) {
-        TimeCapabilities capabilities = in.readParcelable(null);
-        TimeConfiguration configuration = in.readParcelable(null);
+        TimeCapabilities capabilities = in.readParcelable(null, android.app.time.TimeCapabilities.class);
+        TimeConfiguration configuration = in.readParcelable(null, android.app.time.TimeConfiguration.class);
         return new TimeCapabilitiesAndConfig(capabilities, configuration);
     }
 
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index a9ea76f..cd91b04 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -61,8 +61,8 @@
 
     @NonNull
     private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
-        TimeZoneCapabilities capabilities = in.readParcelable(null);
-        TimeZoneConfiguration configuration = in.readParcelable(null);
+        TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
+        TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
         return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
     }
 
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java
index 6478a2d..8ccff62 100644
--- a/core/java/android/app/timedetector/GnssTimeSuggestion.java
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static GnssTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         GnssTimeSuggestion suggestion = new GnssTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 299e951..1699a5f 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static ManualTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index a5259c2..2030083 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static NetworkTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 6c3a304..52d0bbe 100644
--- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -77,10 +77,10 @@
     private static TelephonyTimeSuggestion createFromParcel(Parcel in) {
         int slotIndex = in.readInt();
         TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
-                .setUtcTime(in.readParcelable(null /* classLoader */))
+                .setUtcTime(in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class))
                 .build();
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index ee88ec54..516ad03 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -195,12 +195,12 @@
 
     private static RulesState createFromParcel(Parcel in) {
         String baseRulesVersion = in.readString();
-        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
+        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null, android.app.timezone.DistroFormatVersion.class);
         boolean operationInProgress = in.readByte() == BYTE_TRUE;
         int distroStagedState = in.readByte();
-        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
+        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
         int installedDistroStatus = in.readByte();
-        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
+        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
         return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress,
                 distroStagedState, stagedDistroRulesVersion,
                 installedDistroStatus, installedDistroRulesVersion);
diff --git a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
index 01a60b1..387319e 100644
--- a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
@@ -65,7 +65,7 @@
         String zoneId = in.readString();
         ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
index eb6750f..e5b4e46 100644
--- a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
@@ -165,7 +165,7 @@
                 .setQuality(in.readInt())
                 .build();
         List<String> debugInfo =
-                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader());
+                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader(), java.lang.String.class);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl
index 65b0249..6b9d2c73 100644
--- a/core/java/android/app/trust/ITrustListener.aidl
+++ b/core/java/android/app/trust/ITrustListener.aidl
@@ -16,13 +16,16 @@
 */
 package android.app.trust;
 
+import java.util.List;
+
 /**
  * Private API to be notified about trust changes.
  *
  * {@hide}
  */
 oneway interface ITrustListener {
-    void onTrustChanged(boolean enabled, int userId, int flags);
+    void onTrustChanged(boolean enabled, int userId, int flags,
+        in List<String> trustGrantedMessages);
     void onTrustManagedChanged(boolean managed, int userId);
     void onTrustError(in CharSequence message);
 }
\ No newline at end of file
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 1291766c..edabccf 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -26,6 +26,7 @@
  */
 interface ITrustManager {
     void reportUnlockAttempt(boolean successful, int userId);
+    void reportUserRequestedUnlock(int userId);
     void reportUnlockLockout(int timeoutMs, int userId);
     void reportEnabledTrustAgentsChanged(int userId);
     void registerTrustListener(in ITrustListener trustListener);
diff --git a/core/java/android/app/trust/OWNERS b/core/java/android/app/trust/OWNERS
new file mode 100644
index 0000000..e2c6ce1
--- /dev/null
+++ b/core/java/android/app/trust/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/trust/OWNERS
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index e214007..70b7de0 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -29,6 +29,9 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * See {@link com.android.server.trust.TrustManagerService}
  * @hide
@@ -43,6 +46,7 @@
     private static final String TAG = "TrustManager";
     private static final String DATA_FLAGS = "initiatedByUser";
     private static final String DATA_MESSAGE = "message";
+    private static final String DATA_GRANTED_MESSAGES = "grantedMessages";
 
     private final ITrustManager mService;
     private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
@@ -85,6 +89,19 @@
     }
 
     /**
+     * Reports that the user {@code userId} is likely interested in unlocking the device.
+     *
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     */
+    public void reportUserRequestedUnlock(int userId) {
+        try {
+            mService.reportUserRequestedUnlock(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Reports that user {@param userId} has entered a temporary device lockout.
      *
      * This generally occurs when  the user has unsuccessfully tried to unlock the device too many
@@ -139,12 +156,15 @@
         try {
             ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
                 @Override
-                public void onTrustChanged(boolean enabled, int userId, int flags) {
+                public void onTrustChanged(boolean enabled, int userId, int flags,
+                        List<String> trustGrantedMessages) {
                     Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId,
                             trustListener);
                     if (flags != 0) {
                         m.getData().putInt(DATA_FLAGS, flags);
                     }
+                    m.getData().putCharSequenceArrayList(
+                            DATA_GRANTED_MESSAGES, (ArrayList) trustGrantedMessages);
                     m.sendToTarget();
                 }
 
@@ -231,14 +251,15 @@
             switch(msg.what) {
                 case MSG_TRUST_CHANGED:
                     int flags = msg.peekData() != null ? msg.peekData().getInt(DATA_FLAGS) : 0;
-                    ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags);
+                    ((TrustListener) msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags,
+                            msg.getData().getStringArrayList(DATA_GRANTED_MESSAGES));
                     break;
                 case MSG_TRUST_MANAGED_CHANGED:
                     ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2);
                     break;
                 case MSG_TRUST_ERROR:
                     final CharSequence message = msg.peekData().getCharSequence(DATA_MESSAGE);
-                    ((TrustListener)msg.obj).onTrustError(message);
+                    ((TrustListener) msg.obj).onTrustError(message);
             }
         }
     };
@@ -252,8 +273,11 @@
          * @param flags Flags specified by the trust agent when granting trust. See
          *     {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int)
          *                 TrustAgentService.grantTrust(CharSequence, long, int)}.
+         * @param trustGrantedMessages Messages to display to the user when trust has been granted
+         *        by one or more trust agents.
          */
-        void onTrustChanged(boolean enabled, int userId, int flags);
+        void onTrustChanged(boolean enabled, int userId, int flags,
+                List<String> trustGrantedMessages);
 
         /**
          * Reports that whether trust is managed has changed
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 0ccb058..ba6bcdc 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -148,7 +148,7 @@
                     return builder.setVolumeUuid(in.readString())
                             .setUid(in.readInt())
                             .setQuota(in.readLong())
-                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
+                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader(), android.app.usage.UsageStats.class))
                             .build();
                 }
 
diff --git a/core/java/android/app/wallpapereffectsgeneration/OWNERS b/core/java/android/app/wallpapereffectsgeneration/OWNERS
new file mode 100644
index 0000000..2bc0154
--- /dev/null
+++ b/core/java/android/app/wallpapereffectsgeneration/OWNERS
@@ -0,0 +1,5 @@
+susharon@google.com
+shanh@google.com
+huiwu@google.com
+srazdan@google.com
+
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 18a59d8..1d2f06d 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -595,7 +595,7 @@
         boolean forceConfirmation = (flg & 0x20) != 0;
         boolean skipPrompt = (flg & 0x400) != 0;
         List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
-        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader());
+        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class<android.companion.DeviceFilter<?>>) (Class<?>) android.companion.DeviceFilter.class);
         String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
         CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
         String packageName = (flg & 0x40) == 0 ? null : in.readString();
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index be663f7..e0018f4 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -70,7 +70,7 @@
     }
 
     private static List<ParcelUuid> readUuids(Parcel in) {
-        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());
+        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
     }
 
     /** @hide */
diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java
index 58898cc..e6091f0 100644
--- a/core/java/android/companion/BluetoothLeDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLeDeviceFilter.java
@@ -252,7 +252,7 @@
         public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
             Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null));
+                    .setScanFilter(in.readParcelable(null, android.bluetooth.le.ScanFilter.class));
             byte[] rawDataFilter = in.createByteArray();
             byte[] rawDataFilterMask = in.createByteArray();
             if (rawDataFilter != null) {
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 85855be..339e9a2 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -75,4 +76,5 @@
      */
     void launchPendingIntent(
             int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver);
+    PointF getCursorPosition(IBinder token);
 }
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 8ab6688..64f16ac 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -16,7 +16,6 @@
 
 package android.companion.virtual;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -26,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;
@@ -41,12 +41,9 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.util.ArrayMap;
 import android.view.Surface;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
 import java.util.concurrent.Executor;
 
 /**
@@ -61,23 +58,6 @@
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "VirtualDeviceManager";
 
-    /** @hide */
-    @IntDef(prefix = "DISPLAY_FLAG_",
-            flag = true,
-            value = {DISPLAY_FLAG_TRUSTED})
-    @Retention(RetentionPolicy.SOURCE)
-    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
-    public @interface DisplayFlags {}
-
-    /**
-     * Indicates that the display is trusted to show system decorations and receive inputs without
-     * users' touch.
-     *
-     * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
-     * @hide  // TODO(b/194949534): Unhide this API
-     */
-    public static final int DISPLAY_FLAG_TRUSTED = 1;
-
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
             DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
@@ -102,16 +82,14 @@
      * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
      *   Companion Device Manager. Virtual devices must have a corresponding association with CDM in
      *   order to be created.
-     * @hide
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
-    public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) {
-        // TODO(b/194949534): Unhide this API
+    public VirtualDevice createVirtualDevice(
+            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();
         }
@@ -128,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);
         }
 
         /**
@@ -159,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);
@@ -179,7 +196,9 @@
 
         /**
          * Creates a virtual display for this virtual device. All displays created on the same
-         * device belongs to the same display group.
+         * device belongs to the same display group. Requires the ADD_TRUSTED_DISPLAY permission
+         * to create a virtual display which is not in the default DisplayGroup, and to create
+         * trusted displays.
          *
          * @param width The width of the virtual display in pixels, must be greater than 0.
          * @param height The height of the virtual display in pixels, must be greater than 0.
@@ -187,7 +206,12 @@
          * @param surface The surface to which the content of the virtual display should
          * be rendered, or null if there is none initially. The surface can also be set later using
          * {@link VirtualDisplay#setSurface(Surface)}.
-         * @param flags Either 0, or {@link #DISPLAY_FLAG_TRUSTED}.
+         * @param flags A combination of virtual display flags accepted by
+         * {@link DisplayManager#createVirtualDisplay}. In addition, the following flags are
+         * automatically set for all virtual devices:
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC VIRTUAL_DISPLAY_FLAG_PUBLIC} and
+         * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+         * VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
          * @param callback Callback to call when the state of the {@link VirtualDisplay} changes
          * @param handler The handler on which the listener should be invoked, or null
          * if the listener should be invoked on the calling thread's looper.
@@ -195,9 +219,7 @@
          * not create the virtual display.
          *
          * @see DisplayManager#createVirtualDisplay
-         * @hide
          */
-        // TODO(b/194949534): Unhide this API
         // Suppress "ExecutorRegistration" because DisplayManager.createVirtualDisplay takes a
         // handler
         @SuppressLint("ExecutorRegistration")
@@ -207,7 +229,7 @@
                 int height,
                 int densityDpi,
                 @Nullable Surface surface,
-                @DisplayFlags int flags,
+                int flags,
                 @Nullable Handler handler,
                 @Nullable VirtualDisplay.Callback callback) {
             // TODO(b/205343547): Handle display groups properly instead of creating a new display
@@ -246,7 +268,6 @@
          * @param inputDeviceName the name to call this input device
          * @param vendorId the vendor id
          * @param productId the product id
-         * @hide
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -273,7 +294,6 @@
          * @param inputDeviceName the name to call this input device
          * @param vendorId the vendor id
          * @param productId the product id
-         * @hide
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -300,7 +320,6 @@
          * @param inputDeviceName the name to call this input device
          * @param vendorId the vendor id
          * @param productId the product id
-         * @hide
          */
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         @NonNull
@@ -328,12 +347,8 @@
          * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will
          * be added by DisplayManagerService.
          */
-        private int getVirtualDisplayFlags(@DisplayFlags int flags) {
-            int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
-            if ((flags & DISPLAY_FLAG_TRUSTED) != 0) {
-                virtualDisplayFlags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
-            }
-            return virtualDisplayFlags;
+        private int getVirtualDisplayFlags(int flags) {
+            return DEFAULT_VIRTUAL_DISPLAY_FLAGS | flags;
         }
 
         private String getVirtualDisplayName() {
@@ -347,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);
+        }
     }
 
     /**
@@ -366,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/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d61d474..2ddfeb4 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -20,7 +20,10 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -39,7 +42,7 @@
  *
  * @hide
  */
-// TODO(b/194949534): Unhide this API
+@SystemApi
 public final class VirtualDeviceParams implements Parcelable {
 
     /** @hide */
@@ -51,32 +54,36 @@
 
     /**
      * Indicates that the lock state of the virtual device should be always locked.
-     *
-     * @hide  // TODO(b/194949534): Unhide this API
      */
     public static final int LOCK_STATE_ALWAYS_LOCKED = 0;
 
     /**
      * Indicates that the lock state of the virtual device should be always unlocked.
-     *
-     * @hide  // TODO(b/194949534): Unhide this API
      */
     public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;
 
     private final int mLockState;
     private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
+    @Nullable private final ArraySet<ComponentName> mAllowedActivities;
+    @Nullable private final ArraySet<ComponentName> mBlockedActivities;
 
     private VirtualDeviceParams(
             @LockState int lockState,
-            @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+            @NonNull Set<UserHandle> usersWithMatchingAccounts,
+            @Nullable Set<ComponentName> allowedActivities,
+            @Nullable Set<ComponentName> blockedActivities) {
         mLockState = lockState;
         mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
+        mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities);
+        mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities);
     }
 
     @SuppressWarnings("unchecked")
     private VirtualDeviceParams(Parcel parcel) {
         mLockState = parcel.readInt();
         mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
+        mAllowedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
+        mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
     }
 
     /**
@@ -98,6 +105,35 @@
         return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
     }
 
+    /**
+     * Returns the set of activities allowed to be streamed, or {@code null} if this is not set.
+     *
+     * @see Builder#setAllowedActivities(Set)
+     * @hide  // TODO(b/194949534): Unhide this API
+     */
+    @Nullable
+    public Set<ComponentName> getAllowedActivities() {
+        if (mAllowedActivities == null) {
+            return null;
+        }
+        return Collections.unmodifiableSet(mAllowedActivities);
+    }
+
+    /**
+     * Returns the set of activities that are blocked from streaming, or {@code null} if this is not
+     * set.
+     *
+     * @see Builder#setBlockedActivities(Set)
+     * @hide  // TODO(b/194949534): Unhide this API
+     */
+    @Nullable
+    public Set<ComponentName> getBlockedActivities() {
+        if (mBlockedActivities == null) {
+            return null;
+        }
+        return Collections.unmodifiableSet(mBlockedActivities);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -107,6 +143,8 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mLockState);
         dest.writeArraySet(mUsersWithMatchingAccounts);
+        dest.writeArraySet(mAllowedActivities);
+        dest.writeArraySet(mBlockedActivities);
     }
 
     @Override
@@ -118,8 +156,10 @@
             return false;
         }
         VirtualDeviceParams that = (VirtualDeviceParams) o;
-        return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
-                that.mUsersWithMatchingAccounts);
+        return mLockState == that.mLockState
+                && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
+                && Objects.equals(mAllowedActivities, that.mAllowedActivities)
+                && Objects.equals(mBlockedActivities, that.mBlockedActivities);
     }
 
     @Override
@@ -128,13 +168,17 @@
     }
 
     @Override
+    @NonNull
     public String toString() {
         return "VirtualDeviceParams("
                 + " mLockState=" + mLockState
                 + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+                + " mAllowedActivities=" + mAllowedActivities
+                + " mBlockedActivities=" + mBlockedActivities
                 + ")";
     }
 
+    @NonNull
     public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
             new Parcelable.Creator<VirtualDeviceParams>() {
                 public VirtualDeviceParams createFromParcel(Parcel in) {
@@ -153,6 +197,8 @@
 
         private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
         private Set<UserHandle> mUsersWithMatchingAccounts;
+        @Nullable private Set<ComponentName> mBlockedActivities;
+        @Nullable private Set<ComponentName> mAllowedActivities;
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -171,12 +217,25 @@
 
         /**
          * Sets the user handles with matching managed accounts on the remote device to which
-         * this virtual device is streaming.
+         * this virtual device is streaming. The caller is responsible for verifying the presence
+         * and legitimacy of a matching managed account on the remote device.
+         *
+         * <p>If the app streaming policy is
+         * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+         * NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}, activities not in
+         * {@code usersWithMatchingAccounts} will be blocked from starting.
+         *
+         * <p> If {@code usersWithMatchingAccounts} is empty (the default), streaming is allowed
+         * only if there is no device policy, or if the nearby streaming policy is
+         * {@link android.app.admin.DevicePolicyManager#NEARBY_STREAMING_ENABLED
+         * NEARBY_STREAMING_ENABLED}.
          *
          * @param usersWithMatchingAccounts A set of user handles with matching managed
          *   accounts on the remote device this is streaming to.
+         *
          * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
          */
+        @NonNull
         public Builder setUsersWithMatchingAccounts(
                 @NonNull Set<UserHandle> usersWithMatchingAccounts) {
             mUsersWithMatchingAccounts = usersWithMatchingAccounts;
@@ -184,6 +243,54 @@
         }
 
         /**
+         * Sets the activities allowed to be launched in the virtual device. If
+         * {@code allowedActivities} is non-null, all activities other than the ones in the set will
+         * be blocked from launching.
+         *
+         * <p>{@code allowedActivities} and the set in {@link #setBlockedActivities(Set)} cannot
+         * both be non-null at the same time.
+         *
+         * @throws IllegalArgumentException if {@link #setBlockedActivities(Set)} has been set to a
+         *   non-null value.
+         *
+         * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched
+         *   in the virtual device.
+         * @hide  // TODO(b/194949534): Unhide this API
+         */
+        public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) {
+            if (mBlockedActivities != null && allowedActivities != null) {
+                throw new IllegalArgumentException(
+                        "Allowed activities and Blocked activities cannot both be set.");
+            }
+            mAllowedActivities = allowedActivities;
+            return this;
+        }
+
+        /**
+         * Sets the activities blocked from launching in the virtual device. If the {@code
+         * blockedActivities} is non-null, activities in the set are blocked from launching in the
+         * virtual device.
+         *
+         * {@code blockedActivities} and the set in {@link #setAllowedActivities(Set)} cannot both
+         * be non-null at the same time.
+         *
+         * @throws IllegalArgumentException if {@link #setAllowedActivities(Set)} has been set to a
+         *   non-null value.
+         *
+         * @param blockedActivities A set of {@link ComponentName} to be blocked launching from
+         *   virtual device.
+         * @hide  // TODO(b/194949534): Unhide this API
+         */
+        public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) {
+            if (mAllowedActivities != null && blockedActivities != null) {
+                throw new IllegalArgumentException(
+                        "Allowed activities and Blocked activities cannot both be set.");
+            }
+            mBlockedActivities = blockedActivities;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
          */
         @NonNull
@@ -191,7 +298,13 @@
             if (mUsersWithMatchingAccounts == null) {
                 mUsersWithMatchingAccounts = Collections.emptySet();
             }
-            return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
+            if (mAllowedActivities != null && mBlockedActivities != null) {
+                // Should never reach here because the setters block this as well.
+                throw new IllegalStateException(
+                        "Allowed activities and Blocked activities cannot both be set.");
+            }
+            return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts,
+                    mAllowedActivities, mBlockedActivities);
         }
     }
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c714f507..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;
@@ -48,10 +49,13 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionCheckerManager;
+import android.provider.MediaStore;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -134,9 +138,18 @@
     private boolean mExported;
     private boolean mNoPerms;
     private boolean mSingleUser;
+    private SparseBooleanArray mUsersRedirectedToOwner = new SparseBooleanArray();
 
     private ThreadLocal<AttributionSource> mCallingAttributionSource;
 
+    /**
+     * @hide
+     */
+    public static boolean isAuthorityRedirectedForCloneProfile(String authority) {
+        // For now, only MediaProvider gets redirected.
+        return MediaStore.AUTHORITY.equals(authority);
+    }
+
     private Transport mTransport = new Transport();
 
     /**
@@ -726,13 +739,47 @@
     }
 
     boolean checkUser(int pid, int uid, Context context) {
-        if (UserHandle.getUserId(uid) == context.getUserId() || mSingleUser) {
+        final int callingUserId = UserHandle.getUserId(uid);
+
+        if (callingUserId == context.getUserId() || mSingleUser) {
             return true;
         }
-        return context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
-                    == PackageManager.PERMISSION_GRANTED
+        if (context.checkPermission(INTERACT_ACROSS_USERS, pid, uid)
+                == PackageManager.PERMISSION_GRANTED
                 || context.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid)
-                    == PackageManager.PERMISSION_GRANTED;
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+
+        if (isAuthorityRedirectedForCloneProfile(mAuthority)) {
+            if (mUsersRedirectedToOwner.indexOfKey(callingUserId) >= 0) {
+                return mUsersRedirectedToOwner.get(callingUserId);
+            }
+
+            // Haven't seen this user yet, look it up
+            try {
+                UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
+                Context callingUserContext = mContext.createPackageContextAsUser("system",
+                        0, callingUser);
+                UserManager um = callingUserContext.getSystemService(UserManager.class);
+
+                if (um != null && um.isCloneProfile()) {
+                    UserHandle parent = um.getProfileParent(callingUser);
+
+                    if (parent != null && parent.equals(myUserHandle())) {
+                        mUsersRedirectedToOwner.put(callingUserId, true);
+                        return true;
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore
+            }
+
+            mUsersRedirectedToOwner.put(callingUserId, false);
+            return false;
+        }
+
+        return false;
     }
 
     /**
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 30775b1..0c065d9b 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -108,7 +108,7 @@
             mExtras = null;
         }
         mSelection = source.readInt() != 0 ? source.readString8() : null;
-        mSelectionArgs = source.readSparseArray(null);
+        mSelectionArgs = source.readSparseArray(null, java.lang.Object.class);
         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
         mYieldAllowed = source.readInt() != 0;
         mExceptionAllowed = source.readInt() != 0;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2309fb6..ce2efcf 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -41,6 +41,7 @@
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
+import android.app.ambientcontext.AmbientContextManager;
 import android.app.people.PeopleManager;
 import android.app.time.TimeManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -3829,7 +3830,7 @@
             PRINT_SERVICE,
             CONSUMER_IR_SERVICE,
             //@hide: TRUST_SERVICE,
-            TV_IAPP_SERVICE,
+            TV_INTERACTIVE_APP_SERVICE,
             TV_INPUT_SERVICE,
             //@hide: TV_TUNER_RESOURCE_MGR_SERVICE,
             //@hide: NETWORK_SCORE_SERVICE,
@@ -5356,13 +5357,13 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.media.tv.interactive.TvIAppManager} for interacting with TV interactive
-     * applications (TV iApp) on the device.
+     * {@link android.media.tv.interactive.TvInteractiveAppManager} for interacting with TV
+     * interactive applications on the device.
      *
      * @see #getSystemService(String)
-     * @see android.media.tv.interactive.TvIAppManager
+     * @see android.media.tv.interactive.TvInteractiveAppManager
      */
-    public static final String TV_IAPP_SERVICE = "tv_iapp";
+    public static final String TV_INTERACTIVE_APP_SERVICE = "tv_interactive_app";
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
@@ -5931,6 +5932,17 @@
     public static final String NEARBY_SERVICE = "nearby";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.app.ambientcontext.AmbientContextManager}.
+     *
+     * @see #getSystemService(String)
+     * @see AmbientContextManager
+     * @hide
+     */
+    @SystemApi
+    public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6417,10 +6429,10 @@
      * Triggers the asynchronous revocation of a permission.
      *
      * @param permName The name of the permission to be revoked.
-     * @see #selfRevokePermissions(Collection)
+     * @see #revokeOwnPermissionsOnKill(Collection)
      */
-    public void selfRevokePermission(@NonNull String permName) {
-        selfRevokePermissions(Collections.singletonList(permName));
+    public void revokeOwnPermissionOnKill(@NonNull String permName) {
+        revokeOwnPermissionsOnKill(Collections.singletonList(permName));
     }
 
     /**
@@ -6445,7 +6457,7 @@
      * @see PackageManager#getGroupOfPlatformPermission(String, Executor, Consumer)
      * @see PackageManager#getPlatformPermissionsForGroup(String, Executor, Consumer)
      */
-    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+    public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) {
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
 
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 805e499..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;
     }
@@ -1016,8 +1048,8 @@
     }
 
     @Override
-    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
-        mBase.selfRevokePermissions(permissions);
+    public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) {
+        mBase.revokeOwnPermissionsOnKill(permissions);
     }
 
     @Override
@@ -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/Intent.java b/core/java/android/content/Intent.java
index 58a7d87..7f00bcb 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8190,6 +8190,37 @@
                     hasIntentInfo = true;
                 }
                 break;
+                case "--ed": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    intent.putExtra(key, Double.valueOf(value));
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--eda": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    double[] list = new double[strings.length];
+                    for (int i = 0; i < strings.length; i++) {
+                        list[i] = Double.valueOf(strings[i]);
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
+                case "--edal": {
+                    String key = cmd.getNextArgRequired();
+                    String value = cmd.getNextArgRequired();
+                    String[] strings = value.split(",");
+                    ArrayList<Double> list = new ArrayList<>(strings.length);
+                    for (int i = 0; i < strings.length; i++) {
+                        list.add(Double.valueOf(strings[i]));
+                    }
+                    intent.putExtra(key, list);
+                    hasIntentInfo = true;
+                }
+                break;
                 case "--esa": {
                     String key = cmd.getNextArgRequired();
                     String value = cmd.getNextArgRequired();
@@ -8435,25 +8466,30 @@
                 "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]",
                 "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]",
                 "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]",
+                "    [--ed <EXTRA_KEY> <EXTRA_DOUBLE_VALUE> ...]",
                 "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]",
                 "    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]",
                 "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
-                "        (mutiple extras passed as Integer[])",
+                "        (multiple extras passed as Integer[])",
                 "    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]",
-                "        (mutiple extras passed as List<Integer>)",
+                "        (multiple extras passed as List<Integer>)",
                 "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
-                "        (mutiple extras passed as Long[])",
+                "        (multiple extras passed as Long[])",
                 "    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]",
-                "        (mutiple extras passed as List<Long>)",
+                "        (multiple extras passed as List<Long>)",
                 "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
-                "        (mutiple extras passed as Float[])",
+                "        (multiple extras passed as Float[])",
                 "    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]",
-                "        (mutiple extras passed as List<Float>)",
+                "        (multiple extras passed as List<Float>)",
+                "    [--eda <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]",
+                "        (multiple extras passed as Double[])",
+                "    [--edal <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]",
+                "        (multiple extras passed as List<Double>)",
                 "    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
-                "        (mutiple extras passed as String[]; to embed a comma into a string,",
+                "        (multiple extras passed as String[]; to embed a comma into a string,",
                 "         escape it using \"\\,\")",
                 "    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]",
-                "        (mutiple extras passed as List<String>; to embed a comma into a string,",
+                "        (multiple extras passed as List<String>; to embed a comma into a string,",
                 "         escape it using \"\\,\")",
                 "    [-f <FLAG>]",
                 "    [--grant-read-uri-permission] [--grant-write-uri-permission]",
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 432e81b..6830f5f 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -84,7 +84,7 @@
     }
 
     private PeriodicSync(Parcel in) {
-        this.account = in.readParcelable(null);
+        this.account = in.readParcelable(null, android.accounts.Account.class);
         this.authority = in.readString();
         this.extras = in.readBundle();
         this.period = in.readLong();
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index 017a92b..57101be 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -99,7 +99,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     SyncInfo(Parcel parcel) {
         authorityId = parcel.readInt();
-        account = parcel.readParcelable(Account.class.getClassLoader());
+        account = parcel.readParcelable(Account.class.getClassLoader(), android.accounts.Account.class);
         authority = parcel.readString();
         startTime = parcel.readLong();
     }
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index e1e6f75..83ce84e 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -174,7 +174,7 @@
         mIsAuthority = (in.readInt() != 0);
         mIsExpedited = (in.readInt() != 0);
         mIsScheduledAsExpeditedJob = (in.readInt() != 0);
-        mAccountToSync = in.readParcelable(null);
+        mAccountToSync = in.readParcelable(null, android.accounts.Account.class);
         mAuthority = in.readString();
     }
 
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/UndoManager.java b/core/java/android/content/UndoManager.java
index 87afbf8..b2979f3 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -777,7 +777,7 @@
             final int N = p.readInt();
             for (int i=0; i<N; i++) {
                 UndoOwner owner = mManager.restoreOwner(p);
-                UndoOperation op = (UndoOperation)p.readParcelable(loader);
+                UndoOperation op = (UndoOperation)p.readParcelable(loader, android.content.UndoOperation.class);
                 op.mOwner = owner;
                 mOperations.add(op);
             }
diff --git a/core/java/android/content/UriPermission.java b/core/java/android/content/UriPermission.java
index d3a9cb8..7347761 100644
--- a/core/java/android/content/UriPermission.java
+++ b/core/java/android/content/UriPermission.java
@@ -47,7 +47,7 @@
 
     /** {@hide} */
     public UriPermission(Parcel in) {
-        mUri = in.readParcelable(null);
+        mUri = in.readParcelable(null, android.net.Uri.class);
         mModeFlags = in.readInt();
         mPersistedTime = in.readLong();
     }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 73be0ff..868dab2 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -67,7 +67,7 @@
         mRequests = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final int request = source.readInt();
-            final OverlayIdentifier overlay = source.readParcelable(null);
+            final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class);
             final int userId = source.readInt();
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
diff --git a/core/java/android/content/pm/AppSearchShortcutInfo.java b/core/java/android/content/pm/AppSearchShortcutInfo.java
index 806091e..8d9ef853 100644
--- a/core/java/android/content/pm/AppSearchShortcutInfo.java
+++ b/core/java/android/content/pm/AppSearchShortcutInfo.java
@@ -423,7 +423,7 @@
                 shortLabelResName, longLabel, longLabelResId, longLabelResName, disabledMessage,
                 disabledMessageResId, disabledMessageResName, categoriesSet, intents, rank, extras,
                 getCreationTimestampMillis(), flags, iconResId, iconResName, bitmapPath, iconUri,
-                disabledReason, persons, locusId, null);
+                disabledReason, persons, locusId, null, null);
         si.setImplicitRank(implicitRank);
         if ((implicitRank & ShortcutInfo.RANK_CHANGED_BIT) != 0) {
             si.setRankChanged();
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 11b2ea1..1e88758 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -15,6 +15,9 @@
  */
 package android.content.pm;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -22,6 +25,7 @@
 import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.AppOpsManager.Mode;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -241,12 +245,24 @@
     public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) {
         verifyCanAccessUser(userHandle);
 
-        final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier())
-                ? R.string.managed_profile_label
-                : R.string.user_owner_label;
+        final boolean isManagedProfile = mUserManager.isManagedProfile(userHandle.getIdentifier());
+        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(
+                getUpdatableProfileSwitchingLabelId(isManagedProfile),
+                () -> getDefaultProfileSwitchingLabel(isManagedProfile));
+    }
+
+    private String getUpdatableProfileSwitchingLabelId(boolean isManagedProfile) {
+        return isManagedProfile ? SWITCH_TO_WORK_LABEL : SWITCH_TO_PERSONAL_LABEL;
+    }
+
+    private String getDefaultProfileSwitchingLabel(boolean isManagedProfile) {
+        final int stringRes = isManagedProfile
+                ? R.string.managed_profile_label : R.string.user_owner_label;
         return mResources.getString(stringRes);
     }
 
+
     /**
      * Return a drawable that calling app can show to user for the semantic of profile switching --
      * launching its own activity in specified user profile. For example, it may return a briefcase
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index a45bf79..84d2ca3 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -61,7 +61,7 @@
 
     private InstallSourceInfo(Parcel source) {
         mInitiatingPackageName = source.readString();
-        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
+        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader(), android.content.pm.SigningInfo.class);
         mOriginatingPackageName = source.readString();
         mInstallingPackageName = source.readString();
     }
diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java
index 24d6a07..d6cfb0e 100644
--- a/core/java/android/content/pm/InstantAppInfo.java
+++ b/core/java/android/content/pm/InstantAppInfo.java
@@ -65,7 +65,7 @@
         mLabelText = parcel.readCharSequence();
         mRequestedPermissions = parcel.readStringArray();
         mGrantedPermissions = parcel.createStringArray();
-        mApplicationInfo = parcel.readParcelable(null);
+        mApplicationInfo = parcel.readParcelable(null, android.content.pm.ApplicationInfo.class);
     }
 
     /**
diff --git a/core/java/android/content/pm/InstantAppIntentFilter.java b/core/java/android/content/pm/InstantAppIntentFilter.java
index 123d2ba..721b261 100644
--- a/core/java/android/content/pm/InstantAppIntentFilter.java
+++ b/core/java/android/content/pm/InstantAppIntentFilter.java
@@ -46,7 +46,7 @@
 
     InstantAppIntentFilter(Parcel in) {
         mSplitName = in.readString();
-        in.readList(mFilters, getClass().getClassLoader());
+        in.readList(mFilters, getClass().getClassLoader(), android.content.IntentFilter.class);
     }
 
     public String getSplitName() {
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 9881564..6124638 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -140,7 +140,7 @@
             mFilters = Collections.emptyList();
             mVersionCode = -1;
         } else {
-            mDigest = in.readParcelable(null /*loader*/);
+            mDigest = in.readParcelable(null /*loader*/, android.content.pm.InstantAppResolveInfo.InstantAppDigest.class);
             mPackageName = in.readString();
             mFilters = new ArrayList<>();
             in.readTypedList(mFilters, InstantAppIntentFilter.CREATOR);
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 417f168..46c415d 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -43,10 +43,10 @@
     }
 
     public LauncherActivityInfoInternal(Parcel source) {
-        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader());
+        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
         mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
         mIncrementalStatesInfo = source.readParcelable(
-                IncrementalStatesInfo.class.getClassLoader());
+                IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
     }
 
     public ComponentName getComponentName() {
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d76dc78..08b07a7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1754,11 +1754,11 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null);
+            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
             appLabel = source.readString();
-            originatingUri = source.readParcelable(null);
+            originatingUri = source.readParcelable(null, android.net.Uri.class);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null);
+            referrerUri = source.readParcelable(null, android.net.Uri.class);
             abiOverride = source.readString();
             volumeUuid = source.readString();
             grantedRuntimePermissions = source.readStringArray();
@@ -1770,7 +1770,7 @@
             forceQueryableOverride = source.readBoolean();
             requiredInstalledVersionCode = source.readLong();
             DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
-                    DataLoaderParamsParcel.class.getClassLoader());
+                    DataLoaderParamsParcel.class.getClassLoader(), android.content.pm.DataLoaderParamsParcel.class);
             if (dataLoaderParamsParcel != null) {
                 dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
             }
@@ -2563,13 +2563,13 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null);
+            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
             appLabel = source.readString();
 
             installLocation = source.readInt();
-            originatingUri = source.readParcelable(null);
+            originatingUri = source.readParcelable(null, android.net.Uri.class);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null);
+            referrerUri = source.readParcelable(null, android.net.Uri.class);
             grantedRuntimePermissions = source.readStringArray();
             whitelistedRestrictedPermissions = source.createStringArrayList();
             autoRevokePermissionsMode = source.readInt();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c8f88f2..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) {
@@ -4077,6 +4078,14 @@
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_DREAM_OVERLAY = "android.software.dream_overlay";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports window magnification.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WINDOW_MAGNIFICATION =
+            "android.software.window_magnification";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 701c546..98cc8f6 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7300,7 +7300,7 @@
             splitFlags = dest.createIntArray();
             splitPrivateFlags = dest.createIntArray();
             baseHardwareAccelerated = (dest.readInt() == 1);
-            applicationInfo = dest.readParcelable(boot);
+            applicationInfo = dest.readParcelable(boot, android.content.pm.ApplicationInfo.class);
             if (applicationInfo.permission != null) {
                 applicationInfo.permission = applicationInfo.permission.intern();
             }
@@ -7308,19 +7308,19 @@
             // We don't serialize the "owner" package and the application info object for each of
             // these components, in order to save space and to avoid circular dependencies while
             // serialization. We need to fix them all up here.
-            dest.readParcelableList(permissions, boot);
+            dest.readParcelableList(permissions, boot, android.content.pm.PackageParser.Permission.class);
             fixupOwner(permissions);
-            dest.readParcelableList(permissionGroups, boot);
+            dest.readParcelableList(permissionGroups, boot, android.content.pm.PackageParser.PermissionGroup.class);
             fixupOwner(permissionGroups);
-            dest.readParcelableList(activities, boot);
+            dest.readParcelableList(activities, boot, android.content.pm.PackageParser.Activity.class);
             fixupOwner(activities);
-            dest.readParcelableList(receivers, boot);
+            dest.readParcelableList(receivers, boot, android.content.pm.PackageParser.Activity.class);
             fixupOwner(receivers);
-            dest.readParcelableList(providers, boot);
+            dest.readParcelableList(providers, boot, android.content.pm.PackageParser.Provider.class);
             fixupOwner(providers);
-            dest.readParcelableList(services, boot);
+            dest.readParcelableList(services, boot, android.content.pm.PackageParser.Service.class);
             fixupOwner(services);
-            dest.readParcelableList(instrumentation, boot);
+            dest.readParcelableList(instrumentation, boot, android.content.pm.PackageParser.Instrumentation.class);
             fixupOwner(instrumentation);
 
             dest.readStringList(requestedPermissions);
@@ -7330,10 +7330,10 @@
             protectedBroadcasts = dest.createStringArrayList();
             internStringArrayList(protectedBroadcasts);
 
-            parentPackage = dest.readParcelable(boot);
+            parentPackage = dest.readParcelable(boot, android.content.pm.PackageParser.Package.class);
 
             childPackages = new ArrayList<>();
-            dest.readParcelableList(childPackages, boot);
+            dest.readParcelableList(childPackages, boot, android.content.pm.PackageParser.Package.class);
             if (childPackages.size() == 0) {
                 childPackages = null;
             }
@@ -7367,7 +7367,7 @@
             }
 
             preferredActivityFilters = new ArrayList<>();
-            dest.readParcelableList(preferredActivityFilters, boot);
+            dest.readParcelableList(preferredActivityFilters, boot, android.content.pm.PackageParser.ActivityIntentInfo.class);
             if (preferredActivityFilters.size() == 0) {
                 preferredActivityFilters = null;
             }
@@ -7388,7 +7388,7 @@
             }
             mSharedUserLabel = dest.readInt();
 
-            mSigningDetails = dest.readParcelable(boot);
+            mSigningDetails = dest.readParcelable(boot, android.content.pm.PackageParser.SigningDetails.class);
 
             mPreferredOrder = dest.readInt();
 
@@ -7400,19 +7400,19 @@
 
 
             configPreferences = new ArrayList<>();
-            dest.readParcelableList(configPreferences, boot);
+            dest.readParcelableList(configPreferences, boot, android.content.pm.ConfigurationInfo.class);
             if (configPreferences.size() == 0) {
                 configPreferences = null;
             }
 
             reqFeatures = new ArrayList<>();
-            dest.readParcelableList(reqFeatures, boot);
+            dest.readParcelableList(reqFeatures, boot, android.content.pm.FeatureInfo.class);
             if (reqFeatures.size() == 0) {
                 reqFeatures = null;
             }
 
             featureGroups = new ArrayList<>();
-            dest.readParcelableList(featureGroups, boot);
+            dest.readParcelableList(featureGroups, boot, android.content.pm.FeatureGroupInfo.class);
             if (featureGroups.size() == 0) {
                 featureGroups = null;
             }
@@ -7809,13 +7809,13 @@
         private Permission(Parcel in) {
             super(in);
             final ClassLoader boot = Object.class.getClassLoader();
-            info = in.readParcelable(boot);
+            info = in.readParcelable(boot, android.content.pm.PermissionInfo.class);
             if (info.group != null) {
                 info.group = info.group.intern();
             }
 
             tree = (in.readInt() == 1);
-            group = in.readParcelable(boot);
+            group = in.readParcelable(boot, android.content.pm.PackageParser.PermissionGroup.class);
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Permission>() {
@@ -7870,7 +7870,7 @@
 
         private PermissionGroup(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.PermissionGroupInfo.class);
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<PermissionGroup>() {
@@ -8163,7 +8163,7 @@
 
         private Activity(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ActivityInfo.class);
             mHasMaxAspectRatio = in.readBoolean();
             mHasMinAspectRatio = in.readBoolean();
 
@@ -8257,7 +8257,7 @@
 
         private Service(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ServiceInfo.class);
 
             for (ServiceIntentInfo aii : intents) {
                 aii.service = this;
@@ -8347,7 +8347,7 @@
 
         private Provider(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ProviderInfo.class);
             syncable = (in.readInt() == 1);
 
             for (ProviderIntentInfo aii : intents) {
@@ -8439,7 +8439,7 @@
 
         private Instrumentation(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.InstrumentationInfo.class);
 
             if (info.targetPackage != null) {
                 info.targetPackage = info.targetPackage.intern();
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index f153566..43a4b17 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -136,8 +136,8 @@
         mName = parcel.readString8();
         mVersion = parcel.readLong();
         mType = parcel.readInt();
-        mDeclaringPackage = parcel.readParcelable(null);
-        mDependentPackages = parcel.readArrayList(null);
+        mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
+        mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
     }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 613fb84..ab827aa 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -41,6 +41,7 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.contentcapture.ContentCaptureContext;
@@ -50,9 +51,15 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Represents a shortcut that can be published via {@link ShortcutManager}.
@@ -463,6 +470,9 @@
 
     private int mExcludedSurfaces;
 
+    @Nullable
+    private Map<String, Map<String, List<String>>> mCapabilityBindings;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -490,7 +500,7 @@
         mRank = b.mRank;
         mExtras = b.mExtras;
         mLocusId = b.mLocusId;
-
+        mCapabilityBindings = b.mCapabilityBindings;
         mStartingThemeResName = b.mStartingThemeResId != 0
                 ? b.mContext.getResources().getResourceName(b.mStartingThemeResId) : null;
         updateTimestamp();
@@ -602,7 +612,7 @@
         mLocusId = source.mLocusId;
         mExcludedSurfaces = source.mExcludedSurfaces;
 
-        // Just always keep it since it's cheep.
+        // Just always keep it since it's cheap.
         mIconResId = source.mIconResId;
 
         if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
@@ -641,6 +651,7 @@
             // Set this bit.
             mFlags |= FLAG_KEY_FIELDS_ONLY;
         }
+        mCapabilityBindings = source.mCapabilityBindings;
         mStartingThemeResName = source.mStartingThemeResName;
     }
 
@@ -968,6 +979,9 @@
         if (source.mStartingThemeResName != null && !source.mStartingThemeResName.isEmpty()) {
             mStartingThemeResName = source.mStartingThemeResName;
         }
+        if (source.mCapabilityBindings != null) {
+            mCapabilityBindings = source.mCapabilityBindings;
+        }
     }
 
     /**
@@ -1039,6 +1053,9 @@
 
         private int mStartingThemeResId;
 
+        @Nullable
+        private Map<String, Map<String, List<String>>> mCapabilityBindings;
+
         private int mExcludedSurfaces;
 
         /**
@@ -1401,6 +1418,53 @@
         }
 
         /**
+         * Associates a shortcut with a capability, and a parameter of that capability. Used when
+         * the shortcut is an instance of a capability.
+         *
+         * <P>This method can be called multiple times to add multiple parameters to the same
+         * capability.
+         *
+         * @param capability capability associated with the shortcut. e.g. actions.intent
+         *                   .START_EXERCISE.
+         * @param parameterName name of the parameter associated with given capability.
+         *                      e.g. exercise.name.
+         * @param parameterValues a list of values for that parameters. The first value will be
+         *                        the primary name, while the rest will be alternative names. If
+         *                        the values are empty, then the parameter will not be saved in
+         *                        the shortcut.
+         */
+        @NonNull
+        public Builder addCapabilityBinding(@NonNull String capability,
+                @Nullable String parameterName, @Nullable List<String> parameterValues) {
+            Objects.requireNonNull(capability);
+            if (capability.contains("/")) {
+                throw new IllegalArgumentException("Illegal character '/' is found in capability");
+            }
+            if (mCapabilityBindings == null) {
+                mCapabilityBindings = new ArrayMap<>(1);
+            }
+            if (!mCapabilityBindings.containsKey(capability)) {
+                mCapabilityBindings.put(capability, new ArrayMap<>(0));
+            }
+            if (parameterName == null || parameterValues == null || parameterValues.isEmpty()) {
+                return this;
+            }
+            if (parameterName.contains("/")) {
+                throw new IllegalArgumentException(
+                        "Illegal character '/' is found in parameter name");
+            }
+            final Map<String, List<String>> params = mCapabilityBindings.get(capability);
+            if (!params.containsKey(parameterName)) {
+                params.put(parameterName, parameterValues);
+                return this;
+            }
+            params.put(parameterName,
+                    Stream.of(params.get(parameterName), parameterValues)
+                            .flatMap(Collection::stream).collect(Collectors.toList()));
+            return this;
+        }
+
+        /**
          * Sets which surfaces a shortcut will be excluded from.
          *
          * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be
@@ -2176,13 +2240,51 @@
         return (mExcludedSurfaces & surface) == 0;
     }
 
+    /**
+     * @hide
+     */
+    public Map<String, Map<String, List<String>>> getCapabilityBindings() {
+        return mCapabilityBindings;
+    }
+
+    /**
+     * Return true if the shortcut is or can be used in specified capability.
+     */
+    public boolean hasCapability(@NonNull String capability) {
+        Objects.requireNonNull(capability);
+        return mCapabilityBindings != null && mCapabilityBindings.containsKey(capability);
+    }
+
+    /**
+     *  Returns the values of specified parameter in associated with given capability.
+     *
+     *  @param capability capability associated with the shortcut. e.g. actions.intent
+     *                   .START_EXERCISE.
+     *  @param parameterName name of the parameter associated with given capability.
+     *                       e.g. exercise.name.
+     */
+    @NonNull
+    public List<String> getCapabilityParameterValues(
+            @NonNull String capability, @NonNull String parameterName) {
+        Objects.requireNonNull(capability);
+        Objects.requireNonNull(parameterName);
+        if (mCapabilityBindings == null) {
+            return Collections.emptyList();
+        }
+        final Map<String, List<String>> param = mCapabilityBindings.get(capability);
+        if (param == null || !param.containsKey(parameterName)) {
+            return Collections.emptyList();
+        }
+        return param.get(parameterName);
+    }
+
     private ShortcutInfo(Parcel source) {
         final ClassLoader cl = getClass().getClassLoader();
 
         mUserId = source.readInt();
         mId = source.readString8();
         mPackageName = source.readString8();
-        mActivity = source.readParcelable(cl);
+        mActivity = source.readParcelable(cl, android.content.ComponentName.class);
         mFlags = source.readInt();
         mIconResId = source.readInt();
         mLastChangedTimestamp = source.readLong();
@@ -2192,7 +2294,7 @@
             return; // key information only.
         }
 
-        mIcon = source.readParcelable(cl);
+        mIcon = source.readParcelable(cl, android.graphics.drawable.Icon.class);
         mTitle = source.readCharSequence();
         mTitleResId = source.readInt();
         mText = source.readCharSequence();
@@ -2202,7 +2304,7 @@
         mIntents = source.readParcelableArray(cl, Intent.class);
         mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
         mRank = source.readInt();
-        mExtras = source.readParcelable(cl);
+        mExtras = source.readParcelable(cl, android.os.PersistableBundle.class);
         mBitmapPath = source.readString8();
 
         mIconResName = source.readString8();
@@ -2221,10 +2323,19 @@
         }
 
         mPersons = source.readParcelableArray(cl, Person.class);
-        mLocusId = source.readParcelable(cl);
+        mLocusId = source.readParcelable(cl, android.content.LocusId.class);
         mIconUri = source.readString8();
         mStartingThemeResName = source.readString8();
         mExcludedSurfaces = source.readInt();
+
+        final Map<String, Map> rawCapabilityBindings = source.readHashMap(
+                /*loader*/ null, /*clazzKey*/ String.class, /*clazzValue*/ HashMap.class);
+        if (rawCapabilityBindings != null && !rawCapabilityBindings.isEmpty()) {
+            final Map<String, Map<String, List<String>>> capabilityBindings =
+                    new ArrayMap<>(rawCapabilityBindings.size());
+            rawCapabilityBindings.forEach(capabilityBindings::put);
+            mCapabilityBindings = capabilityBindings;
+        }
     }
 
     @Override
@@ -2278,6 +2389,7 @@
         dest.writeString8(mIconUri);
         dest.writeString8(mStartingThemeResName);
         dest.writeInt(mExcludedSurfaces);
+        dest.writeMap(mCapabilityBindings);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2529,7 +2641,8 @@
             long lastChangedTimestamp,
             int flags, int iconResId, String iconResName, String bitmapPath, String iconUri,
             int disabledReason, Person[] persons, LocusId locusId,
-            @Nullable String startingThemeResName) {
+            @Nullable String startingThemeResName,
+            @Nullable Map<String, Map<String, List<String>>> capabilityBindings) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -2559,5 +2672,6 @@
         mPersons = persons;
         mLocusId = locusId;
         mStartingThemeResName = startingThemeResName;
+        mCapabilityBindings = capabilityBindings;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index be0d934..7dbfd08 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -704,8 +704,8 @@
         }
 
         private ShareShortcutInfo(@NonNull Parcel in) {
-            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
-            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader());
+            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
+            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
         }
 
         @NonNull
diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java
index c613441..64337d8 100644
--- a/core/java/android/content/pm/ShortcutQueryWrapper.java
+++ b/core/java/android/content/pm/ShortcutQueryWrapper.java
@@ -143,7 +143,7 @@
         List<LocusId> locusIds = null;
         if ((flg & 0x8) != 0) {
             locusIds = new ArrayList<>();
-            in.readParcelableList(locusIds, LocusId.class.getClassLoader());
+            in.readParcelableList(locusIds, LocusId.class.getClassLoader(), android.content.LocusId.class);
         }
         ComponentName activity = (flg & 0x10) == 0 ? null
                 : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
index 3e69ff5..868bb9c 100644
--- a/core/java/android/content/pm/VerifierInfo.java
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -59,7 +59,7 @@
 
     private VerifierInfo(Parcel source) {
         packageName = source.readString();
-        publicKey = (PublicKey) source.readSerializable();
+        publicKey = (PublicKey) source.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class);
     }
 
     @Override
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/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index cda1638..dae09f0 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -235,7 +235,7 @@
             public Family createFromParcel(Parcel source) {
                 String familyName = source.readString8();
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader());
+                        new ArrayList<>(), Font.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Font.class);
                 return new Family(familyName, fonts);
             }
 
@@ -379,9 +379,9 @@
 
     protected FontUpdateRequest(Parcel in) {
         mType = in.readInt();
-        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
         mSignature = in.readBlob();
-        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
+        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Family.class);
     }
 
     public @Type int getType() {
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 4683d25..acceb654 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -110,9 +110,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @LongDef(flag = true, value = {USAGE_CPU_READ_RARELY, USAGE_CPU_READ_OFTEN,
             USAGE_CPU_WRITE_RARELY, USAGE_CPU_WRITE_OFTEN, USAGE_GPU_SAMPLED_IMAGE,
-            USAGE_GPU_COLOR_OUTPUT, USAGE_PROTECTED_CONTENT, USAGE_VIDEO_ENCODE,
-            USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA, USAGE_GPU_CUBE_MAP,
-            USAGE_GPU_MIPMAP_COMPLETE})
+            USAGE_GPU_COLOR_OUTPUT, USAGE_COMPOSER_OVERLAY, USAGE_PROTECTED_CONTENT,
+            USAGE_VIDEO_ENCODE, USAGE_GPU_DATA_BUFFER, USAGE_SENSOR_DIRECT_DATA,
+            USAGE_GPU_CUBE_MAP, USAGE_GPU_MIPMAP_COMPLETE, USAGE_FRONT_BUFFER})
     public @interface Usage {};
 
     @Usage
@@ -151,6 +151,12 @@
     public static final long USAGE_GPU_CUBE_MAP           = 1 << 25;
     /** Usage: The buffer contains a complete mipmap hierarchy */
     public static final long USAGE_GPU_MIPMAP_COMPLETE    = 1 << 26;
+    /** Usage: The buffer is used for front-buffer rendering. When front-buffering rendering is
+     * specified, different usages may adjust their behavior as a result. For example, when
+     * used as USAGE_GPU_COLOR_OUTPUT the buffer will behave similar to a single-buffered window.
+     * When used with USAGE_COMPOSER_OVERLAY, the system will try to prioritize the buffer
+     * receiving an overlay plane & avoid caching it in intermediate composition buffers. */
+    public static final long USAGE_FRONT_BUFFER           = 1 << 32;
 
     /**
      * Creates a new <code>HardwareBuffer</code> instance.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index e6b762a..0c03948 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -65,7 +65,7 @@
         mAuthenticators = in.readInt();
         mDisallowBiometricsIfPolicyExists = in.readBoolean();
         mReceiveSystemEvents = in.readBoolean();
-        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader());
+        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
         mAllowBackgroundAuthentication = in.readBoolean();
         mIgnoreEnrollmentState = in.readBoolean();
     }
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
index f365ee6..1490ea1 100644
--- a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
@@ -60,7 +60,7 @@
         sensorStrength = in.readInt();
         maxEnrollmentsPerUser = in.readInt();
         componentInfo = new ArrayList<>();
-        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader());
+        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader(), android.hardware.biometrics.ComponentInfoInternal.class);
         resetLockoutRequiresHardwareAuthToken = in.readBoolean();
         resetLockoutRequiresChallenge = in.readBoolean();
     }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 89ac8bf..eefa1d3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -334,6 +334,7 @@
      * @hide
      */
     @TestApi
+    @SystemApi
     public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
 
     /**
diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java
index f39d634..a53aad7 100644
--- a/core/java/android/hardware/face/FaceAuthenticationFrame.java
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java
@@ -46,7 +46,7 @@
     }
 
     private FaceAuthenticationFrame(@NonNull Parcel source) {
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
     }
 
     @Override
diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java
index 822a579..bbccee2 100644
--- a/core/java/android/hardware/face/FaceEnrollFrame.java
+++ b/core/java/android/hardware/face/FaceEnrollFrame.java
@@ -73,9 +73,9 @@
     }
 
     private FaceEnrollFrame(@NonNull Parcel source) {
-        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader());
+        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader(), android.hardware.face.FaceEnrollCell.class);
         mStage = source.readInt();
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
     }
 
     @Override
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 1173c31..ad6c12b 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.annotation.NonNull;
+import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
 import android.os.IBinder;
 import android.view.InputEvent;
@@ -79,6 +80,28 @@
     public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
             @NonNull IBinder toChannelToken);
 
+    /**
+     * Sets the display id that the MouseCursorController will be forced to target. Pass
+     * {@link android.view.Display#INVALID_DISPLAY} to clear the override.
+     */
+    public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
+
+    /** Gets the current position of the mouse cursor. */
+    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.
+     */
+    public abstract void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible);
+
     /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */
     public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks);
 
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
index 6599dd2..6e2b56a 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.companion.virtual.IVirtualDevice;
+import android.graphics.PointF;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.MotionEvent;
@@ -61,6 +62,8 @@
      * Send a mouse button event to the system.
      *
      * @param event the event
+     * @throws IllegalStateException if the display this mouse is associated with is not currently
+     * targeted
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) {
@@ -76,6 +79,8 @@
      * {@link MotionEvent#AXIS_SCROLL}.
      *
      * @param event the event
+     * @throws IllegalStateException if the display this mouse is associated with is not currently
+     * targeted
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) {
@@ -90,6 +95,8 @@
      * Sends a relative movement event to the system.
      *
      * @param event the event
+     * @throws IllegalStateException if the display this mouse is associated with is not currently
+     * targeted
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) {
@@ -99,4 +106,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Gets the current cursor position.
+     *
+     * @return the position, expressed as x and y coordinates
+     * @throws IllegalStateException if the display this mouse is associated with is not currently
+     * targeted
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public @NonNull PointF getCursorPosition() {
+        try {
+            return mVirtualDevice.getCursorPosition(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index 78cca96..310ebe9 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -81,7 +81,7 @@
                     int monitoringType = source.readInt();
                     int monitoringStatus = source.readInt();
                     int sourceTechnologies = source.readInt();
-                    Location location = source.readParcelable(classLoader);
+                    Location location = source.readParcelable(classLoader, android.location.Location.class);
 
                     return new GeofenceHardwareMonitorEvent(
                             monitoringType,
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 7f07af7..459dab1 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.ParcelableUsbPort;
@@ -136,7 +137,10 @@
     void resetUsbGadget();
 
     /* Set USB data on or off */
-    boolean enableUsbDataSignal(boolean enable);
+    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();
@@ -156,9 +160,14 @@
     /* Sets the port's current role. */
     void setPortRoles(in String portId, int powerRole, int dataRole);
 
+    /* Limit power transfer in & out of the port within the allowed limit by the USB
+     * specification.
+     */
+    void enableLimitPowerTransfer(in String portId, boolean limit, int operationId, in IUsbOperationInternal callback);
+
     /* Enable/disable contaminant detection */
     void enableContaminantDetection(in String portId, boolean enable);
 
-   /* Sets USB device connection handler. */
-   void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
+    /* Sets USB device connection handler. */
+    void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/hardware/usb/IUsbOperationInternal.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/hardware/usb/IUsbOperationInternal.aidl
index 2b3e961..3f3bbf6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/hardware/usb/IUsbOperationInternal.aidl
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.hardware.usb;
 
-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
+ */
+oneway interface IUsbOperationInternal {
+void onOperationComplete(in int status);
+}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index c29a948..f0e040e 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -36,6 +36,8 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -48,6 +50,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -517,6 +520,14 @@
     public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
 
     /**
+     * Returned when the client has to retry querying the version.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_RETRY = -2;
+
+    /**
      * The Value for USB hal is not presented.
      *
      * {@hide}
@@ -557,6 +568,14 @@
     public static final int USB_HAL_V1_3 = 13;
 
     /**
+     * Value for USB Hal Version v2.0.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int USB_HAL_V2_0 = 20;
+
+    /**
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
@@ -665,6 +684,7 @@
             USB_HAL_V1_1,
             USB_HAL_V1_2,
             USB_HAL_V1_3,
+            USB_HAL_V2_0,
     })
     public @interface UsbHalVersion {}
 
@@ -1169,8 +1189,9 @@
     /**
      * Enable/Disable the USB data signaling.
      * <p>
-     * Enables/Disables USB data path in all the USB ports.
+     * Enables/Disables USB data path of the first port..
      * It will force to stop or restore USB data signaling.
+     * Call UsbPort API if the device has more than one UsbPort.
      * </p>
      *
      * @param enable enable or disable USB data signaling
@@ -1181,11 +1202,11 @@
      */
     @RequiresPermission(Manifest.permission.MANAGE_USB)
     public boolean enableUsbDataSignal(boolean enable) {
-        try {
-            return mService.enableUsbDataSignal(enable);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        List<UsbPort> usbPorts = getPorts();
+        if (usbPorts.size() == 1) {
+            return usbPorts.get(0).enableUsbData(enable) == UsbPort.ENABLE_USB_DATA_SUCCESS;
         }
+        return false;
     }
 
     /**
@@ -1271,6 +1292,102 @@
     }
 
     /**
+     * Should only be called by {@link UsbPort#enableLimitPowerTransfer}.
+     * <p>
+     * limits or restores power transfer in and out of USB port.
+     *
+     * @param port USB port for which power transfer has to be limited or restored.
+     * @param limit limit power transfer when true.
+     *              relax power transfer restrictions when false.
+     * @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 enableLimitPowerTransfer(@NonNull UsbPort port, boolean limit, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(port, "enableLimitPowerTransfer:port must not be null. opId:"
+                + operationId);
+        try {
+            mService.enableLimitPowerTransfer(port.getId(), limit, operationId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "enableLimitPowerTransfer failed. opId:" + operationId, e);
+            try {
+                callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL);
+            } catch (RemoteException r) {
+                Log.e(TAG, "enableLimitPowerTransfer failed to call onOperationComplete. opId:"
+                        + operationId, r);
+            }
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Should only be called by {@link UsbPort#enableUsbData}.
+     * <p>
+     * Enables or disables USB data on the specific port.
+     *
+     * @param port USB port for which USB data needs to be enabled or disabled.
+     * @param enable Enable USB data when true.
+     *               Disable USB data when false.
+     * @param operationId operationId for the request.
+     * @param callback callback object to be invoked when the operation is complete.
+     * @return True when the operation is asynchronous. The caller must therefore call
+     *         {@link UsbOperationInternal#waitForOperationComplete} for processing
+     *         the result.
+     *         False when the operation is synchronous. Caller can proceed reading the result
+     *         through {@link UsbOperationInternal#getStatus}
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    boolean enableUsbData(@NonNull UsbPort port, boolean enable, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(port, "enableUsbData: port must not be null. opId:" + operationId);
+        try {
+            return mService.enableUsbData(port.getId(), enable, operationId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "enableUsbData: failed. opId:" + operationId, e);
+            try {
+                callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL);
+            } catch (RemoteException r) {
+                Log.e(TAG, "enableUsbData: failed to call onOperationComplete. opId:"
+                        + operationId, r);
+            }
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * 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/UsbOperationInternal.java b/core/java/android/hardware/usb/UsbOperationInternal.java
new file mode 100644
index 0000000..9bc2b38
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbOperationInternal.java
@@ -0,0 +1,131 @@
+/*
+ * 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.hardware.usb;
+
+import android.annotation.IntDef;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.TimeUnit;
+/**
+ * UsbOperationInternal allows UsbPort to support both synchronous and
+ * asynchronous function irrespective of whether the underlying hal
+ * method is synchronous or asynchronous.
+ *
+ * @hide
+ */
+public final class UsbOperationInternal extends IUsbOperationInternal.Stub {
+    private static final String TAG = "UsbPortStatus";
+    private final int mOperationID;
+    // Cached portId.
+    private final String mId;
+    // True implies operation did not timeout.
+    private boolean mOperationComplete;
+    private @UsbOperationStatus int mStatus;
+    final ReentrantLock mLock = new ReentrantLock();
+    final Condition mOperationWait  = mLock.newCondition();
+    // Maximum time the caller has to wait for onOperationComplete to be called.
+    private static final int USB_OPERATION_TIMEOUT_MSECS = 5000;
+
+    /**
+     * The requested operation was successfully completed.
+     * Returned in {@link onOperationComplete} and {@link getStatus}.
+     */
+    public static final int USB_OPERATION_SUCCESS = 0;
+
+    /**
+     * The requested operation failed due to internal error.
+     * Returned in {@link onOperationComplete} and {@link getStatus}.
+     */
+    public static final int USB_OPERATION_ERROR_INTERNAL = 1;
+
+    /**
+     * The requested operation failed as it's not supported.
+     * Returned in {@link onOperationComplete} and {@link getStatus}.
+     */
+    public static final int USB_OPERATION_ERROR_NOT_SUPPORTED = 2;
+
+    /**
+     * The requested operation failed as it's not supported.
+     * Returned in {@link onOperationComplete} and {@link getStatus}.
+     */
+    public static final int USB_OPERATION_ERROR_PORT_MISMATCH = 3;
+
+    @IntDef(prefix = { "USB_OPERATION_" }, value = {
+            USB_OPERATION_SUCCESS,
+            USB_OPERATION_ERROR_INTERNAL,
+            USB_OPERATION_ERROR_NOT_SUPPORTED,
+            USB_OPERATION_ERROR_PORT_MISMATCH
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface UsbOperationStatus{}
+
+    UsbOperationInternal(int operationID, String id) {
+        this.mOperationID = operationID;
+        this.mId = id;
+    }
+
+    /**
+     * Hal glue layer would directly call this function when the requested
+     * operation is complete.
+     */
+    @Override
+    public void onOperationComplete(@UsbOperationStatus int status) {
+        mLock.lock();
+        try {
+            mOperationComplete = true;
+            mStatus = status;
+            Log.i(TAG, "Port:" + mId + " opID:" + mOperationID + " status:" + mStatus);
+            mOperationWait.signal();
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    /**
+     * Caller invokes this function to wait for the operation to be complete.
+     */
+    public void waitForOperationComplete() {
+        mLock.lock();
+        try {
+            long now = System.currentTimeMillis();
+            long deadline = now + USB_OPERATION_TIMEOUT_MSECS;
+            // Wait in loop to overcome spurious wakeups.
+            do {
+                mOperationWait.await(deadline - System.currentTimeMillis(),
+                        TimeUnit.MILLISECONDS);
+            } while (!mOperationComplete && System.currentTimeMillis() < deadline);
+            if (!mOperationComplete) {
+                Log.e(TAG, "Port:" + mId + " opID:" + mOperationID
+                        + " operationComplete not received in " + USB_OPERATION_TIMEOUT_MSECS
+                        + "msecs");
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + " operationComplete interrupted");
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    public @UsbOperationStatus int getStatus() {
+        return mOperationComplete ? mStatus : USB_OPERATION_ERROR_INTERNAL;
+    }
+}
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index 274e23f..bef4dea 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,6 +16,10 @@
 
 package android.hardware.usb;
 
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS;
 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED;
 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED;
 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
@@ -29,20 +33,38 @@
 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;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.hardware.usb.UsbOperationInternal;
 import android.hardware.usb.V1_0.Constants;
+import android.os.Binder;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Represents a physical USB port and describes its characteristics.
@@ -51,6 +73,7 @@
  */
 @SystemApi
 public final class UsbPort {
+    private static final String TAG = "UsbPort";
     private final String mId;
     private final int mSupportedModes;
     private final UsbManager mUsbManager;
@@ -64,6 +87,125 @@
      */
     private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE;
 
+    /**
+     * Counter for tracking UsbOperation operations.
+     */
+    private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
+    /**
+     * The {@link #enableUsbData} request was successfully completed.
+     */
+    public static final int ENABLE_USB_DATA_SUCCESS = 0;
+
+    /**
+     * The {@link #enableUsbData} request failed due to internal error.
+     */
+    public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1;
+
+    /**
+     * The {@link #enableUsbData} request failed as it's not supported.
+     */
+    public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2;
+
+    /**
+     * The {@link #enableUsbData} request failed as port id mismatched.
+     */
+    public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3;
+
+    /**
+     * The {@link #enableUsbData} request failed due to other reasons.
+     */
+    public static final int ENABLE_USB_DATA_ERROR_OTHER = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "ENABLE_USB_DATA_" }, value = {
+            ENABLE_USB_DATA_SUCCESS,
+            ENABLE_USB_DATA_ERROR_INTERNAL,
+            ENABLE_USB_DATA_ERROR_NOT_SUPPORTED,
+            ENABLE_USB_DATA_ERROR_PORT_MISMATCH,
+            ENABLE_USB_DATA_ERROR_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface EnableUsbDataStatus{}
+
+    /**
+     * The {@link #enableLimitPowerTransfer} request was successfully completed.
+     */
+    public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0;
+
+    /**
+     * The {@link #enableLimitPowerTransfer} request failed due to internal error.
+     */
+    public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1;
+
+    /**
+     * The {@link #enableLimitPowerTransfer} request failed as it's not supported.
+     */
+    public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2;
+
+    /**
+     * The {@link #enableLimitPowerTransfer} request failed as port id mismatched.
+     */
+    public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3;
+
+    /**
+     * The {@link #enableLimitPowerTransfer} request failed due to other reasons.
+     */
+    public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "ENABLE_LIMIT_POWER_TRANSFER_" }, value = {
+            ENABLE_LIMIT_POWER_TRANSFER_SUCCESS,
+            ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL,
+            ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED,
+            ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH,
+            ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER
+    })
+    @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,
@@ -157,7 +299,7 @@
      * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}.
      * </p><p>
      * Note: This function is asynchronous and may fail silently without applying
-     * the requested changes.  If this function does cause a status change to occur then
+     * the operationed changes.  If this function does cause a status change to occur then
      * a {@link UsbManager#ACTION_USB_PORT_CHANGED} broadcast will be sent.
      * </p>
      *
@@ -177,6 +319,133 @@
     }
 
     /**
+     * Enables/Disables Usb data on the port.
+     *
+     * @param enable When true enables USB data if disabled.
+     *               When false disables USB data if enabled.
+     * @return       {@link #ENABLE_USB_DATA_SUCCESS} when request completes successfully or
+     *               {@link #ENABLE_USB_DATA_ERROR_INTERNAL} when request fails due to internal
+     *               error or
+     *               {@link ENABLE_USB_DATA_ERROR_NOT_SUPPORTED} when not supported or
+     *               {@link ENABLE_USB_DATA_ERROR_PORT_MISMATCH} when request fails due to port id
+     *               mismatch or
+     *               {@link ENABLE_USB_DATA_ERROR_OTHER} when fails due to other reasons.
+     */
+    @CheckResult
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public @EnableUsbDataStatus int enableUsbData(boolean enable) {
+        // 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());
+        UsbOperationInternal opCallback =
+                new UsbOperationInternal(operationId, mId);
+        if (mUsbManager.enableUsbData(this, enable, operationId, opCallback) == true) {
+            opCallback.waitForOperationComplete();
+        }
+
+        int result = opCallback.getStatus();
+        switch (result) {
+            case USB_OPERATION_SUCCESS:
+                return ENABLE_USB_DATA_SUCCESS;
+            case USB_OPERATION_ERROR_INTERNAL:
+                return ENABLE_USB_DATA_ERROR_INTERNAL;
+            case USB_OPERATION_ERROR_NOT_SUPPORTED:
+                return ENABLE_USB_DATA_ERROR_NOT_SUPPORTED;
+            case USB_OPERATION_ERROR_PORT_MISMATCH:
+                return ENABLE_USB_DATA_ERROR_PORT_MISMATCH;
+            default:
+                return ENABLE_USB_DATA_ERROR_OTHER;
+        }
+    }
+
+    /**
+     * 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
+     * port disconnect event.
+     * </p>
+     * @param enable limits power transfer when true.
+     * @return {@link #ENABLE_LIMIT_POWER_TRANSFER_SUCCESS} when request completes successfully or
+     *         {@link #ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL} when request fails due to
+     *         internal error or
+     *         {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED} when not supported or
+     *         {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH} when request fails due to
+     *         port id mismatch or
+     *         {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER} when fails due to other reasons.
+     */
+    @CheckResult
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
+    public @EnableLimitPowerTransferStatus int enableLimitPowerTransfer(boolean enable) {
+        // UID is added To minimize operationID overlap between two different packages.
+        int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
+        Log.i(TAG, "enableLimitPowerTransfer opId:" + operationId
+                + " callingUid:" + Binder.getCallingUid());
+        UsbOperationInternal opCallback =
+                new UsbOperationInternal(operationId, mId);
+        mUsbManager.enableLimitPowerTransfer(this, enable, operationId, opCallback);
+        opCallback.waitForOperationComplete();
+        int result = opCallback.getStatus();
+        switch (result) {
+            case USB_OPERATION_SUCCESS:
+                return ENABLE_LIMIT_POWER_TRANSFER_SUCCESS;
+            case USB_OPERATION_ERROR_INTERNAL:
+                return ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL;
+            case USB_OPERATION_ERROR_NOT_SUPPORTED:
+                return ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED;
+            case USB_OPERATION_ERROR_PORT_MISMATCH:
+                return ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH;
+            default:
+                return ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER;
+        }
+    }
+
+    /**
      * @hide
      **/
     public void enableContaminantDetection(boolean enable) {
@@ -274,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 bb7aff6..d1f4246 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -18,8 +18,8 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.hardware.usb.V1_0.Constants;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -36,27 +36,31 @@
 @Immutable
 @SystemApi
 public final class UsbPortStatus implements Parcelable {
+    private static final String TAG = "UsbPortStatus";
     private final int mCurrentMode;
     private final @UsbPowerRole int mCurrentPowerRole;
     private final @UsbDataRole int mCurrentDataRole;
     private final int mSupportedRoleCombinations;
     private final @ContaminantProtectionStatus int mContaminantProtectionStatus;
     private final @ContaminantDetectionStatus int mContaminantDetectionStatus;
+    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.
      */
-    public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE;
+    public static final int POWER_ROLE_NONE = 0;
 
     /**
      * Power role: This USB port can act as a source (provide power).
      */
-    public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE;
+    public static final int POWER_ROLE_SOURCE = 1;
 
     /**
      * Power role: This USB port can act as a sink (receive power).
      */
-    public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK;
+    public static final int POWER_ROLE_SINK = 2;
 
     @IntDef(prefix = { "POWER_ROLE_" }, value = {
             POWER_ROLE_NONE,
@@ -69,17 +73,17 @@
     /**
      * Power role: This USB port does not have a data role.
      */
-    public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE;
+    public static final int DATA_ROLE_NONE = 0;
 
     /**
      * Data role: This USB port can act as a host (access data services).
      */
-    public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST;
+    public static final int DATA_ROLE_HOST = 1;
 
     /**
      * Data role: This USB port can act as a device (offer data services).
      */
-    public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE;
+    public static final int DATA_ROLE_DEVICE = 2;
 
     @IntDef(prefix = { "DATA_ROLE_" }, value = {
             DATA_ROLE_NONE,
@@ -92,15 +96,7 @@
     /**
      * There is currently nothing connected to this USB port.
      */
-    public static final int MODE_NONE = Constants.PortMode.NONE;
-
-    /**
-     * This USB port can act as a downstream facing port (host).
-     *
-     * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and
-     * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well).
-     */
-    public static final int MODE_DFP = Constants.PortMode.DFP;
+    public static final int MODE_NONE = 0;
 
     /**
      * This USB port can act as an upstream facing port (device).
@@ -108,7 +104,15 @@
      * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and
      * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well).
      */
-    public static final int MODE_UFP = Constants.PortMode.UFP;
+    public static final int MODE_UFP = 1 << 0;
+
+    /**
+     * This USB port can act as a downstream facing port (host).
+     *
+     * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and
+     * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well).
+     */
+    public static final int MODE_DFP = 1 << 1;
 
     /**
      * This USB port can act either as an downstream facing port (host) or as
@@ -120,87 +124,127 @@
      *
      * @hide
      */
-    public static final int MODE_DUAL = Constants.PortMode.DRP;
+    public static final int MODE_DUAL = MODE_UFP | MODE_DFP;
 
     /**
      * This USB port can support USB Type-C Audio accessory.
      */
-    public static final int MODE_AUDIO_ACCESSORY =
-            android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY;
+    public static final int MODE_AUDIO_ACCESSORY = 1 << 2;
 
     /**
      * This USB port can support USB Type-C debug accessory.
      */
-    public static final int MODE_DEBUG_ACCESSORY =
-            android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY;
+    public static final int MODE_DEBUG_ACCESSORY = 1 << 3;
 
    /**
      * Contaminant presence detection not supported by the device.
      * @hide
      */
-    public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED =
-            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED;
+    public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = 0;
 
     /**
      * Contaminant presence detection supported but disabled.
      * @hide
      */
-    public static final int CONTAMINANT_DETECTION_DISABLED =
-            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED;
+    public static final int CONTAMINANT_DETECTION_DISABLED = 1;
 
     /**
      * Contaminant presence enabled but not detected.
      * @hide
      */
-    public static final int CONTAMINANT_DETECTION_NOT_DETECTED =
-            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED;
+    public static final int CONTAMINANT_DETECTION_NOT_DETECTED = 2;
 
     /**
      * Contaminant presence enabled and detected.
      * @hide
      */
-    public static final int CONTAMINANT_DETECTION_DETECTED =
-            android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED;
+    public static final int CONTAMINANT_DETECTION_DETECTED = 3;
 
     /**
      * Contaminant protection - No action performed upon detection of
      * contaminant presence.
      * @hide
      */
-    public static final int CONTAMINANT_PROTECTION_NONE =
-            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE;
+    public static final int CONTAMINANT_PROTECTION_NONE = 0;
 
     /**
      * Contaminant protection - Port is forced to sink upon detection of
      * contaminant presence.
      * @hide
      */
-    public static final int CONTAMINANT_PROTECTION_SINK =
-            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK;
+    public static final int CONTAMINANT_PROTECTION_SINK = 1 << 0;
 
     /**
      * Contaminant protection - Port is forced to source upon detection of
      * contaminant presence.
      * @hide
      */
-    public static final int CONTAMINANT_PROTECTION_SOURCE =
-            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE;
+    public static final int CONTAMINANT_PROTECTION_SOURCE = 1 << 1;
 
     /**
      * Contaminant protection - Port is disabled upon detection of
      * contaminant presence.
      * @hide
      */
-    public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE =
-            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE;
+    public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = 1 << 2;
 
     /**
      * Contaminant protection - Port is disabled upon detection of
      * contaminant presence.
      * @hide
      */
-    public static final int CONTAMINANT_PROTECTION_DISABLED =
-            android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED;
+    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,
@@ -232,6 +276,44 @@
     @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, @UsbDataStatus int[] usbDataStatus,
+            boolean powerTransferLimited, @PowerBrickStatus int powerBrickStatus) {
+        mCurrentMode = currentMode;
+        mCurrentPowerRole = currentPowerRole;
+        mCurrentDataRole = currentDataRole;
+        mSupportedRoleCombinations = supportedRoleCombinations;
+        mContaminantProtectionStatus = contaminantProtectionStatus;
+        mContaminantDetectionStatus = contaminantDetectionStatus;
+        mUsbDataStatus = usbDataStatus;
+        mPowerTransferLimited = powerTransferLimited;
+        mPowerBrickStatus = powerBrickStatus;
+    }
+
+    /** @hide */
     public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
             int supportedRoleCombinations, int contaminantProtectionStatus,
             int contaminantDetectionStatus) {
@@ -241,6 +323,9 @@
         mSupportedRoleCombinations = supportedRoleCombinations;
         mContaminantProtectionStatus = contaminantProtectionStatus;
         mContaminantDetectionStatus = contaminantDetectionStatus;
+        mUsbDataStatus = new int[]{USB_DATA_STATUS_UNKNOWN};
+        mPowerBrickStatus = POWER_BRICK_STATUS_UNKNOWN;
+        mPowerTransferLimited = false;
     }
 
     /**
@@ -323,6 +408,40 @@
         return mContaminantProtectionStatus;
     }
 
+    /**
+     * Returns UsbData status.
+     *
+     * @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 @UsbDataStatus @Nullable int[] getUsbDataStatus() {
+        return mUsbDataStatus;
+    }
+
+    /**
+     * Returns whether power transfer is limited.
+     *
+     * @return true when power transfer is limited.
+     *         false otherwise.
+     */
+    public boolean isPowerTransferLimited() {
+        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() {
@@ -336,6 +455,12 @@
                         + getContaminantDetectionStatus()
                 + ", contaminantProtectionStatus="
                         + getContaminantProtectionStatus()
+                + ", usbDataStatus="
+                        + UsbPort.usbDataStatusToString(getUsbDataStatus())
+                + ", isPowerTransferLimited="
+                        + isPowerTransferLimited()
+                +", powerBrickStatus="
+                        + UsbPort.powerBrickStatusToString(getPowerBrickStatus())
                 + "}";
     }
 
@@ -352,6 +477,10 @@
         dest.writeInt(mSupportedRoleCombinations);
         dest.writeInt(mContaminantProtectionStatus);
         dest.writeInt(mContaminantDetectionStatus);
+        dest.writeInt(mUsbDataStatus.length);
+        dest.writeIntArray(mUsbDataStatus);
+        dest.writeBoolean(mPowerTransferLimited);
+        dest.writeInt(mPowerBrickStatus);
     }
 
     public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
@@ -364,9 +493,14 @@
             int supportedRoleCombinations = in.readInt();
             int contaminantProtectionStatus = in.readInt();
             int contaminantDetectionStatus = in.readInt();
+            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);
+                    contaminantDetectionStatus, usbDataStatus, powerTransferLimited,
+                    powerBrickStatus);
         }
 
         @Override
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 6f15588..cc325cd 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -72,7 +72,6 @@
     private static final int DO_START_INPUT = 32;
     private static final int DO_CREATE_SESSION = 40;
     private static final int DO_SET_SESSION_ENABLED = 45;
-    private static final int DO_REVOKE_SESSION = 50;
     private static final int DO_SHOW_SOFT_INPUT = 60;
     private static final int DO_HIDE_SOFT_INPUT = 70;
     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
@@ -215,9 +214,6 @@
                 inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
                         msg.arg1 != 0);
                 return;
-            case DO_REVOKE_SESSION:
-                inputMethod.revokeSession((InputMethodSession)msg.obj);
-                return;
             case DO_SHOW_SOFT_INPUT: {
                 final SomeArgs args = (SomeArgs)msg.obj;
                 inputMethod.showSoftInputWithToken(
@@ -368,22 +364,6 @@
 
     @BinderThread
     @Override
-    public void revokeSession(IInputMethodSession session) {
-        try {
-            InputMethodSession ls = ((IInputMethodSessionWrapper)
-                    session).getInternalInputMethodSession();
-            if (ls == null) {
-                Log.w(TAG, "Session is already finished: " + session);
-                return;
-            }
-            mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
-        } catch (ClassCastException e) {
-            Log.w(TAG, "Incoming session not of correct type: " + session, e);
-        }
-    }
-
-    @BinderThread
-    @Override
     public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver));
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..7bc9573
--- /dev/null
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -0,0 +1,382 @@
+/*
+ * 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 static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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.os.PatternMatcher;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+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;
+
+        @Nullable
+        private BroadcastReceiver mSystemOverlayChangedReceiver;
+
+        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;
+            }
+            if (mNavigationBarFrame != null) {
+                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);
+        }
+
+        private void uninstallNavigationBarFrameIfNecessary() {
+            if (mNavigationBarFrame == null) {
+                return;
+            }
+            final ViewParent parent = mNavigationBarFrame.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(mNavigationBarFrame);
+            }
+            mNavigationBarFrame = 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();
+            if (mSystemOverlayChangedReceiver == null) {
+                final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+                intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+                intentFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
+                mSystemOverlayChangedReceiver = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (mDestroyed) {
+                            return;
+                        }
+                        mRenderGesturalNavButtons = isGesturalNavigationEnabled();
+                        if (mRenderGesturalNavButtons) {
+                            installNavigationBarFrameIfNecessary();
+                        } else {
+                            uninstallNavigationBarFrameIfNecessary();
+                        }
+                    }
+                };
+                mService.registerReceiver(mSystemOverlayChangedReceiver, intentFilter);
+            }
+            installNavigationBarFrameIfNecessary();
+        }
+
+        @Override
+        public void onDestroy() {
+            if (mDestroyed) {
+                return;
+            }
+            if (mSystemOverlayChangedReceiver != null) {
+                mService.unregisterReceiver(mSystemOverlayChangedReceiver);
+                mSystemOverlayChangedReceiver = null;
+            }
+            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
+                    + " mNavigationBarFrame=" + mNavigationBarFrame
+                    + "}";
+        }
+    }
+}
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/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 37425ff..1c4089c 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -160,6 +160,7 @@
         }
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     public static final @android.annotation.NonNull Creator<InterfaceConfiguration> CREATOR = new Creator<
             InterfaceConfiguration>() {
         public InterfaceConfiguration createFromParcel(Parcel in) {
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/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index ab1f542..596f431 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -142,8 +142,8 @@
     }
 
     private NetworkPolicy(Parcel source) {
-        template = source.readParcelable(null);
-        cycleRule = source.readParcelable(null);
+        template = source.readParcelable(null, android.net.NetworkTemplate.class);
+        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
         warningBytes = source.readLong();
         limitBytes = source.readLong();
         lastWarningSnooze = source.readLong();
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index caab152..fd3fe37 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -173,7 +173,7 @@
             new Parcelable.Creator<VcnConfig>() {
                 @NonNull
                 public VcnConfig createFromParcel(Parcel in) {
-                    return new VcnConfig((PersistableBundle) in.readParcelable(null));
+                    return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
                 }
 
                 @NonNull
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/net/vcn/VcnNetworkPolicyResult.java b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
index 14e70cf..fca084a 100644
--- a/core/java/android/net/vcn/VcnNetworkPolicyResult.java
+++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
@@ -114,7 +114,7 @@
     public static final @NonNull Creator<VcnNetworkPolicyResult> CREATOR =
             new Creator<VcnNetworkPolicyResult>() {
                 public VcnNetworkPolicyResult createFromParcel(Parcel in) {
-                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null));
+                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null, android.net.NetworkCapabilities.class));
                 }
 
                 public VcnNetworkPolicyResult[] newArray(int size) {
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 25a2574..5c47b28 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -146,7 +146,7 @@
             new Creator<VcnTransportInfo>() {
                 public VcnTransportInfo createFromParcel(Parcel in) {
                     final int subId = in.readInt();
-                    final WifiInfo wifiInfo = in.readParcelable(null);
+                    final WifiInfo wifiInfo = in.readParcelable(null, android.net.wifi.WifiInfo.class);
 
                     // If all fields are their null values, return null TransportInfo to avoid
                     // leaking information about this being a VCN Network (instead of macro
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
index b0d4f3b..2b5305d 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
@@ -106,7 +106,7 @@
     public static final @NonNull Creator<VcnUnderlyingNetworkPolicy> CREATOR =
             new Creator<VcnUnderlyingNetworkPolicy>() {
                 public VcnUnderlyingNetworkPolicy createFromParcel(Parcel in) {
-                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null));
+                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null, android.net.vcn.VcnNetworkPolicyResult.class));
                 }
 
                 public VcnUnderlyingNetworkPolicy[] newArray(int size) {
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
index ed3b74a..6a40f98 100644
--- a/core/java/android/nfc/BeamShareData.java
+++ b/core/java/android/nfc/BeamShareData.java
@@ -47,13 +47,13 @@
         @Override
         public BeamShareData createFromParcel(Parcel source) {
             Uri[] uris = null;
-            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader());
+            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
             int numUris = source.readInt();
             if (numUris > 0) {
                 uris = new Uri[numUris];
                 source.readTypedArray(uris, Uri.CREATOR);
             }
-            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader());
+            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
             int flags = source.readInt();
 
             return new BeamShareData(msg, uris, userHandle, flags);
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index d2f788f..47a272c 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -192,6 +192,7 @@
             POWER_COMPONENT_CPU,
             POWER_COMPONENT_MOBILE_RADIO,
             POWER_COMPONENT_WIFI,
+            POWER_COMPONENT_BLUETOOTH,
     };
 
     static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a453887..2d33817 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1037,6 +1037,16 @@
         public abstract long getBluetoothMeasuredBatteryConsumptionUC();
 
         /**
+         * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage
+         * when in the specified process state.
+         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+         *
+         * {@hide}
+         */
+        public abstract long getBluetoothMeasuredBatteryConsumptionUC(
+                @BatteryConsumer.ProcessState int processState);
+
+        /**
          * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from
          * on device power measurement data.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
@@ -3396,6 +3406,11 @@
     public abstract WakeLockStats getWakeLockStats();
 
     /**
+     * Returns aggregated Bluetooth stats.
+     */
+    public abstract BluetoothBatteryStats getBluetoothBatteryStats();
+
+    /**
      * Returns Timers tracking the total time of each Resource Power Manager state and voter.
      */
     public abstract Map<String, ? extends Timer> getRpmStats();
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index 6339435..2a609b8 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -368,6 +368,21 @@
     }
 
     /**
+     * Retrieves accumulated bluetooth stats.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+    @NonNull
+    public BluetoothBatteryStats getBluetoothBatteryStats() {
+        try {
+            return mBatteryStats.getBluetoothBatteryStats();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Indicates an app acquiring full wifi lock.
      *
      * @param ws worksource (to be used for battery blaming).
diff --git a/core/java/android/os/BluetoothBatteryStats.aidl b/core/java/android/os/BluetoothBatteryStats.aidl
new file mode 100644
index 0000000..d0514b6
--- /dev/null
+++ b/core/java/android/os/BluetoothBatteryStats.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+/** {@hide} */
+parcelable BluetoothBatteryStats;
diff --git a/core/java/android/os/BluetoothBatteryStats.java b/core/java/android/os/BluetoothBatteryStats.java
new file mode 100644
index 0000000..3d99a08
--- /dev/null
+++ b/core/java/android/os/BluetoothBatteryStats.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Snapshot of Bluetooth battery stats.
+ *
+ * @hide
+ */
+public class BluetoothBatteryStats implements Parcelable {
+
+    /** @hide */
+    public static class UidStats {
+        public final int uid;
+        public final long scanTimeMs;
+        public final long unoptimizedScanTimeMs;
+        public final int scanResultCount;
+        public final long rxTimeMs;
+        public final long txTimeMs;
+
+        public UidStats(int uid, long scanTimeMs, long unoptimizedScanTimeMs, int scanResultCount,
+                long rxTimeMs, long txTimeMs) {
+            this.uid = uid;
+            this.scanTimeMs = scanTimeMs;
+            this.unoptimizedScanTimeMs = unoptimizedScanTimeMs;
+            this.scanResultCount = scanResultCount;
+            this.rxTimeMs = rxTimeMs;
+            this.txTimeMs = txTimeMs;
+        }
+
+        private UidStats(Parcel in) {
+            uid = in.readInt();
+            scanTimeMs = in.readLong();
+            unoptimizedScanTimeMs = in.readLong();
+            scanResultCount = in.readInt();
+            rxTimeMs = in.readLong();
+            txTimeMs = in.readLong();
+        }
+
+        private void writeToParcel(Parcel out) {
+            out.writeInt(uid);
+            out.writeLong(scanTimeMs);
+            out.writeLong(unoptimizedScanTimeMs);
+            out.writeInt(scanResultCount);
+            out.writeLong(rxTimeMs);
+            out.writeLong(txTimeMs);
+        }
+
+        @Override
+        public String toString() {
+            return "UidStats{"
+                    + "uid=" + uid
+                    + ", scanTimeMs=" + scanTimeMs
+                    + ", unoptimizedScanTimeMs=" + unoptimizedScanTimeMs
+                    + ", scanResultCount=" + scanResultCount
+                    + ", rxTimeMs=" + rxTimeMs
+                    + ", txTimeMs=" + txTimeMs
+                    + '}';
+        }
+    }
+
+    private final List<UidStats> mUidStats;
+
+    public BluetoothBatteryStats(@NonNull List<UidStats> uidStats) {
+        mUidStats = uidStats;
+    }
+
+    @NonNull
+    public List<UidStats> getUidStats() {
+        return mUidStats;
+    }
+
+    protected BluetoothBatteryStats(Parcel in) {
+        final int size = in.readInt();
+        mUidStats = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            mUidStats.add(new UidStats(in));
+        }
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        final int size = mUidStats.size();
+        out.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            UidStats stats = mUidStats.get(i);
+            stats.writeToParcel(out);
+        }
+    }
+
+    public static final Creator<BluetoothBatteryStats> CREATOR =
+            new Creator<BluetoothBatteryStats>() {
+                @Override
+                public BluetoothBatteryStats createFromParcel(Parcel in) {
+                    return new BluetoothBatteryStats(in);
+                }
+
+                @Override
+                public BluetoothBatteryStats[] newArray(int size) {
+                    return new BluetoothBatteryStats[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
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/LocaleList.java b/core/java/android/os/LocaleList.java
index cfa823c..b74bb33 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.SuppressLint;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.icu.util.ULocale;
 
@@ -312,18 +313,30 @@
         return isPseudoLocale(locale != null ? locale.toLocale() : null);
     }
 
-    @IntRange(from=0, to=1)
-    private static int matchScore(Locale supported, Locale desired) {
+    /**
+     * Determine whether two locales are considered a match, even if they are not exactly equal.
+     * They are considered as a match when both of their languages and scripts
+     * (explicit or inferred) are identical. This means that a user would be able to understand
+     * the content written in the supported locale even if they say they prefer the desired locale.
+     *
+     * E.g. [zh-HK] matches [zh-Hant]; [en-US] matches [en-CA]
+     *
+     * @param supported The supported {@link Locale} to be compared.
+     * @param desired   The desired {@link Locale} to be compared.
+     * @return True if they match, false otherwise.
+     */
+    public static boolean matchesLanguageAndScript(@SuppressLint("UseIcu") @NonNull
+            Locale supported, @SuppressLint("UseIcu") @NonNull Locale desired) {
         if (supported.equals(desired)) {
-            return 1;  // return early so we don't do unnecessary computation
+            return true;  // return early so we don't do unnecessary computation
         }
         if (!supported.getLanguage().equals(desired.getLanguage())) {
-            return 0;
+            return false;
         }
         if (isPseudoLocale(supported) || isPseudoLocale(desired)) {
             // The locales are not the same, but the languages are the same, and one of the locales
             // is a pseudo-locale. So this is not a match.
-            return 0;
+            return false;
         }
         final String supportedScr = getLikelyScript(supported);
         if (supportedScr.isEmpty()) {
@@ -331,20 +344,17 @@
             // if the locales match. So we fall back to old behavior of matching, which considered
             // locales with different regions different.
             final String supportedRegion = supported.getCountry();
-            return (supportedRegion.isEmpty() ||
-                    supportedRegion.equals(desired.getCountry()))
-                    ? 1 : 0;
+            return supportedRegion.isEmpty() || supportedRegion.equals(desired.getCountry());
         }
         final String desiredScr = getLikelyScript(desired);
         // There is no match if the two locales use different scripts. This will most imporantly
         // take care of traditional vs simplified Chinese.
-        return supportedScr.equals(desiredScr) ? 1 : 0;
+        return supportedScr.equals(desiredScr);
     }
 
     private int findFirstMatchIndex(Locale supportedLocale) {
         for (int idx = 0; idx < mList.length; idx++) {
-            final int score = matchScore(supportedLocale, mList[idx]);
-            if (score > 0) {
+            if (matchesLanguageAndScript(supportedLocale, mList[idx])) {
                 return idx;
             }
         }
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index c62df40..72fb4ae 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -654,7 +654,7 @@
         arg1 = source.readInt();
         arg2 = source.readInt();
         if (source.readInt() != 0) {
-            obj = source.readParcelable(getClass().getClassLoader());
+            obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);
         }
         when = source.readLong();
         data = source.readBundle();
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3bc3ec8..e8b3ae9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3711,10 +3711,10 @@
         final int m = list.size();
         int i = 0;
         for (; i < m && i < n; i++) {
-            list.set(i, (T) readParcelableInternal(cl, clazz));
+            list.set(i, readParcelableInternal(cl, clazz));
         }
         for (; i < n; i++) {
-            list.add((T) readParcelableInternal(cl, clazz));
+            list.add(readParcelableInternal(cl, clazz));
         }
         for (; i < m; i++) {
             list.remove(n);
@@ -4217,7 +4217,8 @@
      * trying to instantiate an element.
      */
     @Nullable
-    public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+    public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
+            @NonNull Class<? super T> clazz) {
         Objects.requireNonNull(clazz);
         return readParcelableInternal(loader, clazz);
     }
@@ -4227,7 +4228,8 @@
      */
     @SuppressWarnings("unchecked")
     @Nullable
-    private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
+    private <T extends Parcelable> T readParcelableInternal(@Nullable ClassLoader loader,
+            @Nullable Class<? super T> clazz) {
         Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
         if (creator == null) {
             return null;
@@ -4463,7 +4465,8 @@
      * deserializing the object.
      */
     @Nullable
-    public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
+    public <T extends Serializable> T readSerializable(@Nullable ClassLoader loader,
+            @NonNull Class<? super T> clazz) {
         Objects.requireNonNull(clazz);
         return readSerializableInternal(
                 loader == null ? getClass().getClassLoader() : loader, clazz);
@@ -4473,8 +4476,8 @@
      * @param clazz The type of the serializable expected or {@code null} for performing no checks
      */
     @Nullable
-    private <T> T readSerializableInternal(@Nullable final ClassLoader loader,
-            @Nullable Class<T> clazz) {
+    private <T extends Serializable> T readSerializableInternal(@Nullable final ClassLoader loader,
+            @Nullable Class<? super T> clazz) {
         String name = readString();
         if (name == null) {
             // For some reason we were unable to read the name of the Serializable (either there
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/Process.java b/core/java/android/os/Process.java
index 742a542..2fe0622 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -132,6 +132,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @TestApi
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final int NFC_UID = 1027;
 
     /**
@@ -279,6 +280,26 @@
     public static final int LAST_APPLICATION_UID = 19999;
 
     /**
+     * Defines the start of a range of UIDs going from this number to
+     * {@link #LAST_SUPPLEMENTAL_UID} that are reserved for assigning to
+     * supplemental processes. There is a 1-1 mapping between a supplemental
+     * process UID and the app that it belongs to, which can be computed by
+     * subtracting (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID) from the
+     * uid of a supplemental process.
+     *
+     * Note that there are no GIDs associated with these processes; storage
+     * attribution for them will be done using project IDs.
+     * @hide
+     */
+    public static final int FIRST_SUPPLEMENTAL_UID = 20000;
+
+    /**
+     * Last UID that is used for supplemental processes.
+     * @hide
+     */
+    public static final int LAST_SUPPLEMENTAL_UID = 29999;
+
+    /**
      * First uid used for fully isolated sandboxed processes spawned from an app zygote
      * @hide
      */
@@ -880,6 +901,46 @@
     }
 
     /**
+     * Returns whether the provided UID belongs to a supplemental process.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final boolean isSupplemental(int uid) {
+        uid = UserHandle.getAppId(uid);
+        return (uid >= FIRST_SUPPLEMENTAL_UID && uid <= LAST_SUPPLEMENTAL_UID);
+    }
+
+    /**
+     *
+     * Returns the app process corresponding to a supplemental process.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int toAppUid(int uid) {
+        return uid - (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+    }
+
+    /**
+     *
+     * Returns the supplemental process corresponding to an app process.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int toSupplementalUid(int uid) {
+        return uid + (FIRST_SUPPLEMENTAL_UID - FIRST_APPLICATION_UID);
+    }
+
+    /**
+     * Returns whether the current process is a supplemental process.
+     */
+    public static final boolean isSupplemental() {
+        return isSupplemental(myUid());
+    }
+
+    /**
      * Returns the UID assigned to a particular user name, or -1 if there is
      * none.  If the given string consists of only numbers, it is converted
      * directly to a uid.
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index ebbfe47..70aaa5e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2993,7 +2993,7 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            mViolation = (Violation) in.readSerializable();
+            mViolation = (Violation) in.readSerializable(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class);
             int binderStackSize = in.readInt();
             for (int i = 0; i < binderStackSize; i++) {
                 StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
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/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 07f4082..22ddbcc 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -31,15 +31,6 @@
       ]
     },
     {
-      "file_patterns": ["Environment\\.java"],
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest"
-        }
-      ]
-    },
-    {
       "file_patterns": [
         "BatteryStats[^/]*\\.java",
         "BatteryUsageStats[^/]*\\.java",
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index d974e0c..6b869f1 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,7 +16,10 @@
 
 package android.os;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -90,6 +93,7 @@
     /** @hide */
     public static final long TRACE_TAG_DATABASE = 1L << 20;
     /** @hide */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final long TRACE_TAG_NETWORK = 1L << 21;
     /** @hide */
     public static final long TRACE_TAG_ADB = 1L << 22;
@@ -148,6 +152,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @SystemApi(client = MODULE_LIBRARIES)
     public static boolean isTagEnabled(long traceTag) {
         long tags = nativeGetEnabledTags();
         return (tags & traceTag) != 0;
@@ -163,7 +168,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static void traceCounter(long traceTag, String counterName, int counterValue) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void traceCounter(long traceTag, @NonNull String counterName, int counterValue) {
         if (isTagEnabled(traceTag)) {
             nativeTraceCounter(traceTag, counterName, counterValue);
         }
@@ -202,7 +208,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static void traceBegin(long traceTag, String methodName) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void traceBegin(long traceTag, @NonNull String methodName) {
         if (isTagEnabled(traceTag)) {
             nativeTraceBegin(traceTag, methodName);
         }
@@ -217,6 +224,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @SystemApi(client = MODULE_LIBRARIES)
     public static void traceEnd(long traceTag) {
         if (isTagEnabled(traceTag)) {
             nativeTraceEnd(traceTag);
@@ -237,7 +245,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static void asyncTraceBegin(long traceTag, String methodName, int cookie) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void asyncTraceBegin(long traceTag, @NonNull String methodName, int cookie) {
         if (isTagEnabled(traceTag)) {
             nativeAsyncTraceBegin(traceTag, methodName, cookie);
         }
@@ -255,7 +264,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static void asyncTraceEnd(long traceTag, String methodName, int cookie) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static void asyncTraceEnd(long traceTag, @NonNull String methodName, int cookie) {
         if (isTagEnabled(traceTag)) {
             nativeAsyncTraceEnd(traceTag, methodName, cookie);
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 190f5f1..bd65a41 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -16,6 +16,9 @@
 
 package android.os;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED;
+
 import android.Manifest;
 import android.accounts.AccountManager;
 import android.annotation.ColorInt;
@@ -820,6 +823,20 @@
     public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
 
     /**
+     * Specifies if a user is disallowed from creating clone profile.
+     * <p>The default value for an unmanaged user is <code>false</code>.
+     * For users with a device owner set, the default is <code>true</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     * @hide
+     */
+    public static final String DISALLOW_ADD_CLONE_PROFILE = "no_add_clone_profile";
+
+    /**
      * Specifies if a user is disallowed from disabling application verification. The default
      * value is <code>false</code>.
      *
@@ -1497,6 +1514,7 @@
             DISALLOW_FACTORY_RESET,
             DISALLOW_ADD_USER,
             DISALLOW_ADD_MANAGED_PROFILE,
+            DISALLOW_ADD_CLONE_PROFILE,
             ENSURE_VERIFY_APPS,
             DISALLOW_CONFIG_CELL_BROADCASTS,
             DISALLOW_CONFIG_MOBILE_NETWORKS,
@@ -4645,6 +4663,18 @@
         if (!hasBadge(userId)) {
             return label;
         }
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(
+                getUpdatableUserBadgedLabelId(userId),
+                () -> getDefaultUserBadgedLabel(label, userId),
+                /* formatArgs= */ label);
+    }
+
+    private String getUpdatableUserBadgedLabelId(int userId) {
+        return isManagedProfile(userId) ? WORK_PROFILE_BADGED_LABEL : UNDEFINED;
+    }
+
+    private String getDefaultUserBadgedLabel(CharSequence label, int userId) {
         try {
             final int resourceId = mService.getUserBadgeLabelResId(userId);
             return Resources.getSystem().getString(resourceId, label);
@@ -4757,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/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 5de4556..ae37a71 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -576,7 +576,7 @@
         private final int mRepeatIndex;
 
         Composed(@NonNull Parcel in) {
-            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
+            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
         }
 
         Composed(@NonNull VibrationEffectSegment segment) {
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 189e454..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());
+        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/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 6588b57..e899f77 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -130,7 +130,7 @@
         int numChains = in.readInt();
         if (numChains > 0) {
             mChains = new ArrayList<>(numChains);
-            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class);
         } else {
             mChains = null;
         }
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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 29accb9..8df659d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -80,6 +80,7 @@
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -1443,28 +1444,39 @@
      *
      * @hide
      */
-    public static final int STORAGE_THRESHOLD_PERCENT_HIGH = 20;
+    public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH = 20;
+    /** {@hide} */
+    @TestApi
+    public static final String
+            STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high";
     /**
      * Devices having below STORAGE_THRESHOLD_PERCENT_LOW of total space free are considered to be
-     * in low free space category.
+     * in low free space category and can be configured via
+     * Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE.
      *
      * @hide
      */
-    public static final int STORAGE_THRESHOLD_PERCENT_LOW = 5;
+    public static final int DEFAULT_STORAGE_THRESHOLD_PERCENT_LOW = 5;
     /**
      * For devices in high free space category, CACHE_RESERVE_PERCENT_HIGH percent of total space is
      * allocated for cache.
      *
      * @hide
      */
-    public static final int CACHE_RESERVE_PERCENT_HIGH = 10;
+    public static final int DEFAULT_CACHE_RESERVE_PERCENT_HIGH = 10;
+    /** {@hide} */
+    @TestApi
+    public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high";
     /**
      * For devices in low free space category, CACHE_RESERVE_PERCENT_LOW percent of total space is
      * allocated for cache.
      *
      * @hide
      */
-    public static final int CACHE_RESERVE_PERCENT_LOW = 2;
+    public static final int DEFAULT_CACHE_RESERVE_PERCENT_LOW = 2;
+    /** {@hide} */
+    @TestApi
+    public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low";
 
     private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
 
@@ -1490,7 +1502,8 @@
     @UnsupportedAppUsage
     public long getStorageLowBytes(File path) {
         final long lowPercent = Settings.Global.getInt(mResolver,
-                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, STORAGE_THRESHOLD_PERCENT_LOW);
+                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+                DEFAULT_STORAGE_THRESHOLD_PERCENT_LOW);
         final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
 
         final long maxLowBytes = Settings.Global.getLong(mResolver,
@@ -1510,24 +1523,33 @@
     @TestApi
     @SuppressLint("StreamFiles")
     public long computeStorageCacheBytes(@NonNull File path) {
+        final int storageThresholdPercentHigh = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                STORAGE_THRESHOLD_PERCENT_HIGH_KEY, DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH);
+        final int cacheReservePercentHigh = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                CACHE_RESERVE_PERCENT_HIGH_KEY, DEFAULT_CACHE_RESERVE_PERCENT_HIGH);
+        final int cacheReservePercentLow = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                CACHE_RESERVE_PERCENT_LOW_KEY, DEFAULT_CACHE_RESERVE_PERCENT_LOW);
         final long totalBytes = path.getTotalSpace();
         final long usableBytes = path.getUsableSpace();
-        final long storageThresholdHighBytes = totalBytes * STORAGE_THRESHOLD_PERCENT_HIGH / 100;
+        final long storageThresholdHighBytes = totalBytes * storageThresholdPercentHigh / 100;
         final long storageThresholdLowBytes = getStorageLowBytes(path);
         long result;
         if (usableBytes > storageThresholdHighBytes) {
-            // If free space is >STORAGE_THRESHOLD_PERCENT_HIGH of total space,
-            // reserve CACHE_RESERVE_PERCENT_HIGH of total space
-            result = totalBytes * CACHE_RESERVE_PERCENT_HIGH / 100;
+            // If free space is >storageThresholdPercentHigh of total space,
+            // reserve cacheReservePercentHigh of total space
+            result = totalBytes * cacheReservePercentHigh / 100;
         } else if (usableBytes < storageThresholdLowBytes) {
-            // If free space is <min(STORAGE_THRESHOLD_PERCENT_LOW of total space, 500MB),
-            // reserve CACHE_RESERVE_PERCENT_LOW of total space
-            result = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100;
+            // If free space is <min(storageThresholdPercentLow of total space, 500MB),
+            // reserve cacheReservePercentLow of total space
+            result = totalBytes * cacheReservePercentLow / 100;
         } else {
             // Else, linearly interpolate the amount of space to reserve
-            double slope = (CACHE_RESERVE_PERCENT_HIGH - CACHE_RESERVE_PERCENT_LOW) * totalBytes
+            double slope = (cacheReservePercentHigh - cacheReservePercentLow) * totalBytes
                     / (100.0 * (storageThresholdHighBytes - storageThresholdLowBytes));
-            double intercept = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100.0
+            double intercept = totalBytes * cacheReservePercentLow / 100.0
                     - storageThresholdLowBytes * slope;
             result = Math.round(slope * usableBytes + intercept);
         }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index b78bb25..8ee52c2 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -168,7 +168,7 @@
         mExternallyManaged = in.readInt() != 0;
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
-        mOwner = in.readParcelable(null);
+        mOwner = in.readParcelable(null, android.os.UserHandle.class);
         if (in.readInt() != 0) {
             mUuid = StorageManager.convert(in.readString8());
         } else {
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 5814bac..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 selfRevokePermissions(in String packageName, in List<String> permissions,
+    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/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 8e5581b..1c0320e 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -76,7 +76,7 @@
 
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
-    void selfRevokePermissions(String packageName, in List<String> permissions);
+    void revokeOwnPermissionsOnKill(String packageName, in List<String> permissions);
 
     void startOneTimePermissionSession(String packageName, int userId, long timeout,
             int importanceToResetTimer, int importanceToKeepSessionAlive);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 47cd107..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>
@@ -835,17 +913,17 @@
      *
      * @param packageName The name of the package for which the permissions will be revoked.
      * @param permissions List of permissions to be revoked.
+     * @param callback Callback called when the revocation request has been completed.
      *
-     * @see Context#selfRevokePermissions(Collection)
+     * @see Context#revokeOwnPermissionsOnKill(Collection)
      *
      * @hide
      */
-    public void selfRevokePermissions(@NonNull String packageName,
-            @NonNull List<String> permissions) {
+    public void revokeOwnPermissionsOnKill(@NonNull String packageName,
+            @NonNull List<String> permissions, AndroidFuture<Void> callback) {
         mRemoteService.postAsync(service -> {
-            AndroidFuture<Void> future = new AndroidFuture<>();
-            service.selfRevokePermissions(packageName, permissions, future);
-            return future;
+            service.revokeOwnPermissionsOnKill(packageName, permissions, callback);
+            return callback;
         }).whenComplete((result, err) -> {
             if (err != null) {
                 Log.e(TAG, "Failed to self revoke " + String.join(",", permissions)
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index dcbab62..8d9f82b 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -337,10 +337,10 @@
      * @param permissions List of permissions to be revoked.
      * @param callback Callback waiting for operation to be complete.
      *
-     * @see PermissionManager#selfRevokePermissions(java.util.Collection)
+     * @see PermissionManager#revokeOwnPermissionsOnKill(java.util.Collection)
      */
     @BinderThread
-    public void onSelfRevokePermissions(@NonNull String packageName,
+    public void onRevokeOwnPermissionsOnKill(@NonNull String packageName,
             @NonNull List<String> permissions, @NonNull Runnable callback) {
         throw new AbstractMethodError("Must be overridden in implementing class");
     }
@@ -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,13 +685,29 @@
             }
 
             @Override
-            public void selfRevokePermissions(@NonNull String packageName,
+            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 {
                     enforceSomePermissionsGrantedToCaller(
                             Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
                     Objects.requireNonNull(callback);
-                    onSelfRevokePermissions(packageName, permissions,
+                    onRevokeOwnPermissionsOnKill(packageName, permissions,
                             () -> callback.complete(null));
                 } catch (Throwable t) {
                     callback.completeExceptionally(t);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 13941dc..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
@@ -562,12 +598,12 @@
     }
 
     /**
-     * @see Context#selfRevokePermissions(Collection)
+     * @see Context#revokeOwnPermissionsOnKill(Collection)
      * @hide
      */
-    public void selfRevokePermissions(@NonNull Collection<String> permissions) {
+    public void revokeOwnPermissionsOnKill(@NonNull Collection<String> permissions) {
         try {
-            mPermissionManager.selfRevokePermissions(mContext.getPackageName(),
+            mPermissionManager.revokeOwnPermissionsOnKill(mContext.getPackageName(),
                     new ArrayList<String>(permissions));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 67249be..9d0c8d8 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -231,9 +231,9 @@
     }
 
     private PrintJobInfo(@NonNull Parcel parcel) {
-        mId = parcel.readParcelable(null);
+        mId = parcel.readParcelable(null, android.print.PrintJobId.class);
         mLabel = parcel.readString();
-        mPrinterId = parcel.readParcelable(null);
+        mPrinterId = parcel.readParcelable(null, android.print.PrinterId.class);
         mPrinterName = parcel.readString();
         mState = parcel.readInt();
         mAppId = parcel.readInt();
@@ -247,8 +247,8 @@
                 mPageRanges[i] = (PageRange) parcelables[i];
             }
         }
-        mAttributes = (PrintAttributes) parcel.readParcelable(null);
-        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
+        mAttributes = (PrintAttributes) parcel.readParcelable(null, android.print.PrintAttributes.class);
+        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null, android.print.PrintDocumentInfo.class);
         mProgress = parcel.readFloat();
         mStatus = parcel.readCharSequence();
         mStatusRes = parcel.readInt();
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 25260c4..284e122 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -48,7 +48,7 @@
     }
 
     private PrinterId(@NonNull Parcel parcel) {
-        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null));
+        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null, android.content.ComponentName.class));
         mLocalId = Preconditions.checkNotNull(parcel.readString());
     }
 
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 8e03e3e..2f93e40 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -270,15 +270,15 @@
     private PrinterInfo(Parcel parcel) {
         // mName can be null due to unchecked set in Builder.setName and status can be invalid
         // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
-        mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
+        mId = checkPrinterId((PrinterId) parcel.readParcelable(null, android.print.PrinterId.class));
         mName = checkName(parcel.readString());
         mStatus = checkStatus(parcel.readInt());
         mDescription = parcel.readString();
-        mCapabilities = parcel.readParcelable(null);
+        mCapabilities = parcel.readParcelable(null, android.print.PrinterCapabilitiesInfo.class);
         mIconResourceId = parcel.readInt();
         mHasCustomPrinterIcon = parcel.readByte() != 0;
         mCustomPrinterIconGen = parcel.readInt();
-        mInfoIntent = parcel.readParcelable(null);
+        mInfoIntent = parcel.readParcelable(null, android.app.PendingIntent.class);
     }
 
     @Override
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 0c1b61d..3479557 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -76,7 +76,7 @@
     public PrintServiceInfo(Parcel parcel) {
         mId = parcel.readString();
         mIsEnabled = parcel.readByte() != 0;
-        mResolveInfo = parcel.readParcelable(null);
+        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
         mSettingsActivityName = parcel.readString();
         mAddPrintersActivityName = parcel.readString();
         mAdvancedPrintOptionsActivityName = parcel.readString();
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 6349cde..87f4489 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -464,6 +464,13 @@
     public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
 
     /**
+     * Namespace for all Supplemental Api related features.
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api";
+
+    /**
      * Namespace for all SurfaceFlinger features that are used at the native level.
      * These features are applied on boot or after reboot.
      *
@@ -687,6 +694,15 @@
     @SystemApi
     public static final String NAMESPACE_UWB = "uwb";
 
+    /**
+     * Namespace for AmbientContextEventManagerService related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE =
+            "ambient_context_manager_service";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bca0286..3f54408 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7168,6 +7168,12 @@
         public static final String LOCATION_COARSE_ACCURACY_M = "locationCoarseAccuracy";
 
         /**
+         * Whether or not to show display system location accesses.
+         * @hide
+         */
+        public static final String LOCATION_SHOW_SYSTEM_OPS = "locationShowSystemOps";
+
+        /**
          * A flag containing settings used for biometric weak
          * @hide
          */
@@ -9057,6 +9063,16 @@
         public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
 
         /**
+         * The complications that are enabled to be shown over the screensaver by the user. Holds
+         * a comma separated list of
+         * {@link com.android.settingslib.dream.DreamBackend.ComplicationType}.
+         *
+         * @hide
+         */
+        public static final String SCREENSAVER_ENABLED_COMPLICATIONS =
+                "screensaver_enabled_complications";
+
+        /**
          * The default NFC payment component
          * @hide
          */
@@ -10586,6 +10602,14 @@
                 "communal_mode_trusted_networks";
 
         /**
+         * Setting to allow Fast Pair scans to be enabled.
+         * @hide
+         */
+        @SystemApi
+        @Readable
+        public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
@@ -12817,16 +12841,6 @@
                 SYS_STORAGE_CACHE_PERCENTAGE = "sys_storage_cache_percentage";
 
         /**
-         * Maximum bytes of storage on the device that is reserved for cached
-         * data.
-         *
-         * @hide
-         */
-        @Readable
-        public static final String
-                SYS_STORAGE_CACHE_MAX_BYTES = "sys_storage_cache_max_bytes";
-
-        /**
          * The maximum reconnect delay for short network outages or when the
          * network is suspended due to phone use.
          *
diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java
new file mode 100644
index 0000000..dccfe36
--- /dev/null
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java
@@ -0,0 +1,137 @@
+/*
+ * 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.ambientcontext;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.util.Slog;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for {@link AmbientContextEvent} detection service.
+ *
+ * <p> A service that provides requested ambient context events to the system.
+ * The system's default AmbientContextDetectionService implementation is configured in
+ * {@code config_defaultAmbientContextDetectionService}. If this config has no value, a stub is
+ * returned.
+ *
+ * See: {@code AmbientContextManagerService}.
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourAmbientContextDetectionService"
+ *          android:permission="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class AmbientContextDetectionService extends Service {
+    private static final String TAG = AmbientContextDetectionService.class.getSimpleName();
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service. To be supported, the
+     * service must also require the
+     * {@link android.Manifest.permission#BIND_AMBIENT_CONTEXT_DETECTION_SERVICE}
+     * permission so that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.ambientcontext.AmbientContextDetectionService";
+
+    /**
+     * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation
+     * should set bundle result with this key.
+     *
+     * @hide
+     */
+    public static final String RESPONSE_BUNDLE_KEY =
+            "android.service.ambientcontext.EventResponseKey";
+
+    @Nullable
+    @Override
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return new IAmbientContextDetectionService.Stub() {
+                /** {@inheritDoc} */
+                @Override
+                public void startDetection(
+                        @NonNull AmbientContextEventRequest request, String packageName,
+                        RemoteCallback callback) {
+                    Objects.requireNonNull(request);
+                    Objects.requireNonNull(callback);
+                    Consumer<AmbientContextEventResponse> consumer =
+                            response -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionService.RESPONSE_BUNDLE_KEY,
+                                        response);
+                                callback.sendResult(bundle);
+                            };
+                    AmbientContextDetectionService.this.onStartDetection(
+                            request, packageName, consumer);
+                    Slog.d(TAG, "startDetection " + request);
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public void stopDetection(String packageName) {
+                    Objects.requireNonNull(packageName);
+                    AmbientContextDetectionService.this.onStopDetection(packageName);
+                }
+            };
+        }
+        return null;
+    }
+
+    /**
+     * Starts detection and provides detected events to the consumer. The ongoing detection will
+     * keep running, until onStopDetection is called. If there were previously requested
+     * detection from the same package, the previous request will be replaced with the new request.
+     * The implementation should keep track of whether the user consented each requested
+     * AmbientContextEvent for the app. If not consented, the response should set status
+     * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user
+     * to the consent screen.
+     *
+     * @param request The request with events to detect, optional detection window and other
+     *                options.
+     * @param packageName the requesting app's package name
+     * @param consumer the consumer for the detected event
+     */
+    public abstract void onStartDetection(
+            @NonNull AmbientContextEventRequest request,
+            @NonNull String packageName,
+            @NonNull Consumer<AmbientContextEventResponse> consumer);
+
+    /**
+     * Stops detection of the events. Events that are not being detected will be ignored.
+     *
+     * @param packageName stops detection for the given package.
+     */
+    public abstract void onStopDetection(@NonNull String packageName);
+}
diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl
new file mode 100644
index 0000000..1c6e25e
--- /dev/null
+++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.service.ambientcontext;
+
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.os.RemoteCallback;
+
+/**
+ * Interface for a concrete implementation to provide AmbientContextEvents to the framework.
+ *
+ * @hide
+ */
+oneway interface IAmbientContextDetectionService {
+    void startDetection(in AmbientContextEventRequest request, in String packageName,
+        in RemoteCallback callback);
+    void stopDetection(in String packageName);
+}
\ No newline at end of file
diff --git a/core/java/android/service/ambientcontext/OWNERS b/core/java/android/service/ambientcontext/OWNERS
new file mode 100644
index 0000000..a863297
--- /dev/null
+++ b/core/java/android/service/ambientcontext/OWNERS
@@ -0,0 +1,3 @@
+enxun@google.com
+kxchen@google.com
+tgadh@google.com
diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java
index 8eeecc2..c996cc0 100644
--- a/core/java/android/service/autofill/BatchUpdates.java
+++ b/core/java/android/service/autofill/BatchUpdates.java
@@ -205,7 +205,7 @@
                     builder.transformChild(ids[i], values[i]);
                 }
             }
-            final RemoteViews updates = parcel.readParcelable(null);
+            final RemoteViews updates = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (updates != null) {
                 builder.updateTemplate(updates);
             }
diff --git a/core/java/android/service/autofill/CompositeUserData.java b/core/java/android/service/autofill/CompositeUserData.java
index 92952cb..55ac5a5 100644
--- a/core/java/android/service/autofill/CompositeUserData.java
+++ b/core/java/android/service/autofill/CompositeUserData.java
@@ -197,8 +197,8 @@
                     // Always go through the builder to ensure the data ingested by
                     // the system obeys the contract of the builder to avoid attacks
                     // using specially crafted parcels.
-                    final UserData genericUserData = parcel.readParcelable(null);
-                    final UserData packageUserData = parcel.readParcelable(null);
+                    final UserData genericUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
+                    final UserData packageUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
                     return new CompositeUserData(genericUserData, packageUserData);
                 }
 
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index f3f912b..690cd06 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -437,7 +437,7 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final RemoteViews parentPresentation = parcel.readParcelable(null);
+            final RemoteViews parentPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (parentPresentation == null) return null;
 
             final Builder builder = new Builder(parentPresentation);
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 8539bf5..86341a9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -913,10 +913,10 @@
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
         @Override
         public Dataset createFromParcel(Parcel parcel) {
-            final RemoteViews presentation = parcel.readParcelable(null);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final InlinePresentation inlineTooltipPresentation =
-                    parcel.readParcelable(null);
+                    parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final ArrayList<AutofillId> ids =
                     parcel.createTypedArrayList(AutofillId.CREATOR);
             final ArrayList<AutofillValue> values =
@@ -929,8 +929,8 @@
                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
             final ArrayList<DatasetFieldFilter> filters =
                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
-            final ClipData fieldContent = parcel.readParcelable(null);
-            final IntentSender authentication = parcel.readParcelable(null);
+            final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
+            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
             final String datasetId = parcel.readString();
 
             // Always go through the builder to ensure the data ingested by
@@ -1014,7 +1014,7 @@
 
             @Override
             public DatasetFieldFilter createFromParcel(Parcel parcel) {
-                return new DatasetFieldFilter((Pattern) parcel.readSerializable());
+                return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
             }
 
             @Override
diff --git a/core/java/android/service/autofill/DateTransformation.java b/core/java/android/service/autofill/DateTransformation.java
index 7340857..df5ed4da 100644
--- a/core/java/android/service/autofill/DateTransformation.java
+++ b/core/java/android/service/autofill/DateTransformation.java
@@ -114,8 +114,8 @@
             new Parcelable.Creator<DateTransformation>() {
         @Override
         public DateTransformation createFromParcel(Parcel parcel) {
-            return new DateTransformation(parcel.readParcelable(null),
-                    (DateFormat) parcel.readSerializable());
+            return new DateTransformation(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
+                    (DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/DateValueSanitizer.java b/core/java/android/service/autofill/DateValueSanitizer.java
index 6f7808e..c7d5b79 100644
--- a/core/java/android/service/autofill/DateValueSanitizer.java
+++ b/core/java/android/service/autofill/DateValueSanitizer.java
@@ -111,7 +111,7 @@
             new Parcelable.Creator<DateValueSanitizer>() {
         @Override
         public DateValueSanitizer createFromParcel(Parcel parcel) {
-            return new DateValueSanitizer((DateFormat) parcel.readSerializable());
+            return new DateValueSanitizer((DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index af846b6..43bd410 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -384,7 +384,7 @@
         byte flg = in.readByte();
         int id = in.readInt();
         List<FillContext> fillContexts = new ArrayList<>();
-        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 970cb18..d94988e 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -834,35 +834,35 @@
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
             final Builder builder = new Builder();
-            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
+            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
             final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
             final int datasetCount = (datasets != null) ? datasets.size() : 0;
             for (int i = 0; i < datasetCount; i++) {
                 builder.addDataset(datasets.get(i));
             }
-            builder.setSaveInfo(parcel.readParcelable(null));
-            builder.setClientState(parcel.readParcelable(null));
+            builder.setSaveInfo(parcel.readParcelable(null, android.service.autofill.SaveInfo.class));
+            builder.setClientState(parcel.readParcelable(null, android.os.Bundle.class));
 
             // Sets authentication state.
             final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
                     AutofillId.class);
-            final IntentSender authentication = parcel.readParcelable(null);
-            final RemoteViews presentation = parcel.readParcelable(null);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
-            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null);
+            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             if (authenticationIds != null) {
                 builder.setAuthentication(authenticationIds, authentication, presentation,
                         inlinePresentation, inlineTooltipPresentation);
             }
-            final RemoteViews header = parcel.readParcelable(null);
+            final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (header != null) {
                 builder.setHeader(header);
             }
-            final RemoteViews footer = parcel.readParcelable(null);
+            final RemoteViews footer = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (footer != null) {
                 builder.setFooter(footer);
             }
-            final UserData userData = parcel.readParcelable(null);
+            final UserData userData = parcel.readParcelable(null, android.service.autofill.UserData.class);
             if (userData != null) {
                 builder.setUserData(userData);
             }
diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java
index e317159..af82205 100644
--- a/core/java/android/service/autofill/ImageTransformation.java
+++ b/core/java/android/service/autofill/ImageTransformation.java
@@ -247,7 +247,7 @@
             new Parcelable.Creator<ImageTransformation>() {
         @Override
         public ImageTransformation createFromParcel(Parcel parcel) {
-            final AutofillId id = parcel.readParcelable(null);
+            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
 
             final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
             final int[] resIds = parcel.createIntArray();
diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java
index d626845..85cd981 100644
--- a/core/java/android/service/autofill/NegationValidator.java
+++ b/core/java/android/service/autofill/NegationValidator.java
@@ -68,7 +68,7 @@
             new Parcelable.Creator<NegationValidator>() {
         @Override
         public NegationValidator createFromParcel(Parcel parcel) {
-            return new NegationValidator(parcel.readParcelable(null));
+            return new NegationValidator(parcel.readParcelable(null, android.service.autofill.InternalValidator.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/RegexValidator.java b/core/java/android/service/autofill/RegexValidator.java
index 00c4347..4c58590 100644
--- a/core/java/android/service/autofill/RegexValidator.java
+++ b/core/java/android/service/autofill/RegexValidator.java
@@ -96,8 +96,8 @@
             new Parcelable.Creator<RegexValidator>() {
         @Override
         public RegexValidator createFromParcel(Parcel parcel) {
-            return new RegexValidator(parcel.readParcelable(null),
-                    (Pattern) parcel.readSerializable());
+            return new RegexValidator(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
+                    (Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 8edfde8..5fe1d4f 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -888,14 +888,14 @@
                 builder.setOptionalIds(optionalIds);
             }
 
-            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
+            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null, android.content.IntentSender.class));
             builder.setPositiveAction(parcel.readInt());
             builder.setDescription(parcel.readCharSequence());
-            final CustomDescription customDescripton = parcel.readParcelable(null);
+            final CustomDescription customDescripton = parcel.readParcelable(null, android.service.autofill.CustomDescription.class);
             if (customDescripton != null) {
                 builder.setCustomDescription(customDescripton);
             }
-            final InternalValidator validator = parcel.readParcelable(null);
+            final InternalValidator validator = parcel.readParcelable(null, android.service.autofill.InternalValidator.class);
             if (validator != null) {
                 builder.setValidator(validator);
             }
@@ -909,7 +909,7 @@
                     builder.addSanitizer(sanitizers[i], autofillIds);
                 }
             }
-            final AutofillId triggerId = parcel.readParcelable(null);
+            final AutofillId triggerId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
             if (triggerId != null) {
                 builder.setTriggerId(triggerId);
             }
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
index 5bafa7a..46c18b2 100644
--- a/core/java/android/service/autofill/TextValueSanitizer.java
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -119,7 +119,7 @@
             new Parcelable.Creator<TextValueSanitizer>() {
         @Override
         public TextValueSanitizer createFromParcel(Parcel parcel) {
-            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class), parcel.readString());
         }
 
         @Override
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index 74a7355..d286942 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -149,7 +149,7 @@
         @Override
         @NonNull
         public ActivityEvent createFromParcel(@NonNull Parcel parcel) {
-            final ComponentName componentName = parcel.readParcelable(null);
+            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
             final int eventType = parcel.readInt();
             return new ActivityEvent(componentName, eventType);
         }
diff --git a/core/java/android/service/contentcapture/SnapshotData.java b/core/java/android/service/contentcapture/SnapshotData.java
index bf469b4..f72624d 100644
--- a/core/java/android/service/contentcapture/SnapshotData.java
+++ b/core/java/android/service/contentcapture/SnapshotData.java
@@ -51,8 +51,8 @@
 
     SnapshotData(@NonNull Parcel parcel) {
         mAssistData = parcel.readBundle();
-        mAssistStructure = parcel.readParcelable(null);
-        mAssistContent = parcel.readParcelable(null);
+        mAssistStructure = parcel.readParcelable(null, android.app.assist.AssistStructure.class);
+        mAssistContent = parcel.readParcelable(null, android.app.assist.AssistContent.class);
     }
 
     /**
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/core/java/android/service/games/CreateGameSessionResult.aidl b/core/java/android/service/games/CreateGameSessionResult.aidl
new file mode 100644
index 0000000..b7c5e16
--- /dev/null
+++ b/core/java/android/service/games/CreateGameSessionResult.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.games;
+
+
+/**
+ * @hide
+ */
+parcelable CreateGameSessionResult;
diff --git a/core/java/android/service/games/CreateGameSessionResult.java b/core/java/android/service/games/CreateGameSessionResult.java
new file mode 100644
index 0000000..8448b0f
--- /dev/null
+++ b/core/java/android/service/games/CreateGameSessionResult.java
@@ -0,0 +1,84 @@
+/*
+ * 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.games;
+
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControlViewHost;
+
+/**
+ * Internal result object that contains the successful creation of a game session.
+ *
+ * @see IGameSessionService#create(CreateGameSessionRequest, GameSessionViewHostConfiguration,
+ * com.android.internal.infra.AndroidFuture)
+ * @hide
+ */
+@Hide
+public final class CreateGameSessionResult implements Parcelable {
+
+    @NonNull
+    public static final Parcelable.Creator<CreateGameSessionResult> CREATOR =
+            new Parcelable.Creator<CreateGameSessionResult>() {
+                @Override
+                public CreateGameSessionResult createFromParcel(Parcel source) {
+                    return new CreateGameSessionResult(
+                            IGameSession.Stub.asInterface(source.readStrongBinder()),
+                            source.readParcelable(
+                                    SurfaceControlViewHost.SurfacePackage.class.getClassLoader(),
+                                    SurfaceControlViewHost.SurfacePackage.class));
+                }
+
+                @Override
+                public CreateGameSessionResult[] newArray(int size) {
+                    return new CreateGameSessionResult[0];
+                }
+            };
+
+    private final IGameSession mGameSession;
+    private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+
+    public CreateGameSessionResult(
+            @NonNull IGameSession gameSession,
+            @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
+        mGameSession = gameSession;
+        mSurfacePackage = surfacePackage;
+    }
+
+    @NonNull
+    public IGameSession getGameSession() {
+        return mGameSession;
+    }
+
+    @NonNull
+    public SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
+        return mSurfacePackage;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mGameSession.asBinder());
+        dest.writeParcelable(mSurfacePackage, flags);
+    }
+}
diff --git a/core/java/android/service/games/GameScreenshotResult.java b/core/java/android/service/games/GameScreenshotResult.java
new file mode 100644
index 0000000..ae76e08
--- /dev/null
+++ b/core/java/android/service/games/GameScreenshotResult.java
@@ -0,0 +1,181 @@
+/*
+ * 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.games;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Result object for calls to {@link IGameSessionController#takeScreenshot}.
+ *
+ * It includes a status (see {@link #getStatus}) and, if the status is
+ * {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link
+ * #getBitmap}).
+ *
+ * @hide
+ */
+public final class GameScreenshotResult implements Parcelable {
+
+    /**
+     * The status of a call to {@link IGameSessionController#takeScreenshot} will be represented by
+     * one of these values.
+     *
+     * @hide
+     */
+    @IntDef(flag = false, prefix = {"GAME_SCREENSHOT_"}, value = {
+            GAME_SCREENSHOT_SUCCESS, // 0
+            GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, // 1
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GameScreenshotStatus {
+    }
+
+    /**
+     * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was
+     * successful and an {@link android.graphics.Bitmap} result should be available by calling
+     * {@link #getBitmap}.
+     *
+     * @hide
+     */
+    public static final int GAME_SCREENSHOT_SUCCESS = 0;
+
+    /**
+     * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} failed
+     * due to an internal error.
+     *
+     * This error may occur if the device is not in a suitable state for a screenshot to be taken
+     * (e.g., the screen is off) or if the game task is not in a suitable state for a screenshot
+     * to be taken (e.g., the task is not visible). To make sure that the device and game are
+     * in a suitable state, the caller can monitor the lifecycle methods for the {@link
+     * GameSession} to make sure that the game task is focused. If the conditions are met, then the
+     * caller may try again immediately.
+     *
+     * @hide
+     */
+    public static final int GAME_SCREENSHOT_ERROR_INTERNAL_ERROR = 1;
+
+    @NonNull
+    public static final Parcelable.Creator<GameScreenshotResult> CREATOR =
+            new Parcelable.Creator<GameScreenshotResult>() {
+                @Override
+                public GameScreenshotResult createFromParcel(Parcel source) {
+                    return new GameScreenshotResult(
+                            source.readInt(),
+                            source.readParcelable(null, Bitmap.class));
+                }
+
+                @Override
+                public GameScreenshotResult[] newArray(int size) {
+                    return new GameScreenshotResult[0];
+                }
+            };
+
+    @GameScreenshotStatus
+    private final int mStatus;
+
+    @Nullable
+    private final Bitmap mBitmap;
+
+    /**
+     * Creates a successful {@link GameScreenshotResult} with the provided bitmap.
+     */
+    public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) {
+        return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap);
+    }
+
+    /**
+     * Creates a failed {@link GameScreenshotResult} with an
+     * {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status.
+     */
+    public static GameScreenshotResult createInternalErrorResult() {
+        return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null);
+    }
+
+    private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) {
+        this.mStatus = status;
+        this.mBitmap = bitmap;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mStatus);
+        dest.writeParcelable(mBitmap, flags);
+    }
+
+    @GameScreenshotStatus
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Gets the {@link Bitmap} result from a successful screenshot attempt.
+     *
+     * @return The bitmap.
+     * @throws IllegalStateException if this method is called when {@link #getStatus} does not
+     *                               return {@link #GAME_SCREENSHOT_SUCCESS}.
+     */
+    @NonNull
+    public Bitmap getBitmap() {
+        if (mBitmap == null) {
+            throw new IllegalStateException("Bitmap not available for failed screenshot result");
+        }
+        return mBitmap;
+    }
+
+    @Override
+    public String toString() {
+        return "GameScreenshotResult{"
+                + "mStatus="
+                + mStatus
+                + ", has bitmap='"
+                + mBitmap != null ? "yes" : "no"
+                + "\'}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof GameScreenshotResult)) {
+            return false;
+        }
+
+        GameScreenshotResult that = (GameScreenshotResult) o;
+        return mStatus == that.mStatus
+                && Objects.equals(mBitmap, that.mBitmap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus, mBitmap);
+    }
+}
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index 0ff08c0..cb5c19b 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -16,11 +16,32 @@
 
 package android.service.games;
 
+import android.annotation.Hide;
+import android.annotation.IntDef;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.function.pooled.PooledLambda;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
 /**
  * An active game session, providing a facility for the implementation to interact with the game.
  *
@@ -28,25 +49,177 @@
  * which is then returned when a game session is created via
  * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}.
  *
+ * This class exposes various lifecycle methods which are guaranteed to be called in the following
+ * fashion:
+ *
+ * {@link #onCreate()}: Will always be the first lifecycle method to be called, once the game
+ * session is created.
+ *
+ * {@link #onGameTaskFocusChanged(boolean)}: Will be called after {@link #onCreate()} with
+ * focused=true when the game task first comes into focus (if it does). If the game task is focused
+ * when the game session is created, this method will be called immediately after
+ * {@link #onCreate()} with focused=true. After this method is called with focused=true, it will be
+ * called again with focused=false when the task goes out of focus. If this method is ever called
+ * with focused=true, it is guaranteed to be called again with focused=false before
+ * {@link #onDestroy()} is called. If the game task never comes into focus during the session
+ * lifetime, this method will never be called.
+ *
+ * {@link #onDestroy()}: Will always be called after {@link #onCreate()}. If the game task ever
+ * comes into focus before the game session is destroyed, then this method will be called after one
+ * or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}.
+ *
  * @hide
  */
 @SystemApi
 public abstract class GameSession {
+    private static final String TAG = "GameSession";
+    private static final boolean DEBUG = false;
 
     final IGameSession mInterface = new IGameSession.Stub() {
         @Override
-        public void destroy() {
+        public void onDestroyed() {
             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
                     GameSession::doDestroy, GameSession.this));
         }
+
+        @Override
+        public void onTaskFocusChanged(boolean focused) {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    GameSession::moveToState, GameSession.this,
+                    focused ? LifecycleState.TASK_FOCUSED : LifecycleState.TASK_UNFOCUSED));
+        }
     };
 
-    void doCreate() {
-        onCreate();
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public enum LifecycleState {
+        // Initial state; may transition to CREATED.
+        INITIALIZED,
+        // May transition to TASK_FOCUSED or DESTROYED.
+        CREATED,
+        // May transition to TASK_UNFOCUSED.
+        TASK_FOCUSED,
+        // May transition to TASK_FOCUSED or DESTROYED.
+        TASK_UNFOCUSED,
+        // May not transition once reached.
+        DESTROYED
     }
 
+    private LifecycleState mLifecycleState = LifecycleState.INITIALIZED;
+    private IGameSessionController mGameSessionController;
+    private int mTaskId;
+    private GameSessionRootView mGameSessionRootView;
+    private SurfaceControlViewHost mSurfaceControlViewHost;
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public void attach(
+            IGameSessionController gameSessionController,
+            int taskId,
+            @NonNull Context context,
+            @NonNull SurfaceControlViewHost surfaceControlViewHost,
+            int widthPx,
+            int heightPx) {
+        mGameSessionController = gameSessionController;
+        mTaskId = taskId;
+        mSurfaceControlViewHost = surfaceControlViewHost;
+        mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost);
+        surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx);
+    }
+
+    @Hide
+    void doCreate() {
+        moveToState(LifecycleState.CREATED);
+    }
+
+    @Hide
     void doDestroy() {
-        onDestroy();
+        mSurfaceControlViewHost.release();
+        moveToState(LifecycleState.DESTROYED);
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    @MainThread
+    public void moveToState(LifecycleState newLifecycleState) {
+        if (DEBUG) {
+            Slog.d(TAG, "moveToState: " + mLifecycleState + " -> " + newLifecycleState);
+        }
+
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new RuntimeException("moveToState should be used only from the main thread");
+        }
+
+        if (mLifecycleState == newLifecycleState) {
+            // Nothing to do.
+            return;
+        }
+
+        switch (mLifecycleState) {
+            case INITIALIZED:
+                if (newLifecycleState == LifecycleState.CREATED) {
+                    onCreate();
+                } else if (newLifecycleState == LifecycleState.DESTROYED) {
+                    onCreate();
+                    onDestroy();
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring moveToState: INITIALIZED -> " + newLifecycleState);
+                    }
+                    return;
+                }
+                break;
+            case CREATED:
+                if (newLifecycleState == LifecycleState.TASK_FOCUSED) {
+                    onGameTaskFocusChanged(/*focused=*/ true);
+                } else if (newLifecycleState == LifecycleState.DESTROYED) {
+                    onDestroy();
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring moveToState: CREATED -> " + newLifecycleState);
+                    }
+                    return;
+                }
+                break;
+            case TASK_FOCUSED:
+                if (newLifecycleState == LifecycleState.TASK_UNFOCUSED) {
+                    onGameTaskFocusChanged(/*focused=*/ false);
+                } else if (newLifecycleState == LifecycleState.DESTROYED) {
+                    onGameTaskFocusChanged(/*focused=*/ false);
+                    onDestroy();
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring moveToState: TASK_FOCUSED -> " + newLifecycleState);
+                    }
+                    return;
+                }
+                break;
+            case TASK_UNFOCUSED:
+                if (newLifecycleState == LifecycleState.TASK_FOCUSED) {
+                    onGameTaskFocusChanged(/*focused=*/ true);
+                } else if (newLifecycleState == LifecycleState.DESTROYED) {
+                    onDestroy();
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring moveToState: TASK_UNFOCUSED -> " + newLifecycleState);
+                    }
+                    return;
+                }
+                break;
+            case DESTROYED:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring moveToState: DESTROYED -> " + newLifecycleState);
+                }
+                return;
+        }
+
+        mLifecycleState = newLifecycleState;
     }
 
     /**
@@ -54,12 +227,173 @@
      *
      * This should be used perform any setup required now that the game session is created.
      */
-    public void onCreate() {}
+    public void onCreate() {
+    }
 
     /**
-     * Finalizer called when the game session is ending.
+     * Finalizer called when the game session is ending. This method will always be called after a
+     * call to {@link #onCreate()}. If the game task is ever in focus, this method will be called
+     * after one or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}.
      *
      * This should be used to perform any cleanup before the game session is destroyed.
      */
-    public void onDestroy() {}
+    public void onDestroy() {
+    }
+
+    /**
+     * Called when the game task for this session is or unfocused. The initial call to this method
+     * will always come after a call to {@link #onCreate()} with focused=true (when the game task
+     * first comes into focus after the session is created, or immediately after the session is
+     * created if the game task is already focused).
+     *
+     * This should be used to perform any setup required when the game task comes into focus or any
+     * cleanup that is required when the game task goes out of focus.
+     *
+     * @param focused True if the game task is focused, false if the game task is unfocused.
+     */
+    public void onGameTaskFocusChanged(boolean focused) {}
+
+    /**
+     * Sets the task overlay content to an explicit view. This view is placed directly into the game
+     * session's task overlay view hierarchy. It can itself be a complex view hierarchy. The size
+     * the task overlay view will always match the dimensions of the associated task's window. The
+     * {@code View} may not be cleared once set, but may be replaced by invoking
+     * {@link #setTaskOverlayView(View, ViewGroup.LayoutParams)} again.
+     *
+     * @param view         The desired content to display.
+     * @param layoutParams Layout parameters for the view.
+     */
+    public void setTaskOverlayView(
+            @NonNull View view,
+            @NonNull ViewGroup.LayoutParams layoutParams) {
+        mGameSessionRootView.removeAllViews();
+        mGameSessionRootView.addView(view, layoutParams);
+    }
+
+    /**
+     * Root view of the {@link SurfaceControlViewHost} associated with the {@link GameSession}
+     * instance. It is responsible for observing changes in the size of the window and resizing
+     * itself to match.
+     */
+    private static final class GameSessionRootView extends FrameLayout {
+        private final SurfaceControlViewHost mSurfaceControlViewHost;
+
+        GameSessionRootView(@NonNull Context context,
+                SurfaceControlViewHost surfaceControlViewHost) {
+            super(context);
+            mSurfaceControlViewHost = surfaceControlViewHost;
+        }
+
+        @Override
+        protected void onConfigurationChanged(Configuration newConfig) {
+            super.onConfigurationChanged(newConfig);
+
+            // TODO(b/204504596): Investigate skipping the relayout in cases where the size has
+            // not changed.
+            Rect bounds = newConfig.windowConfiguration.getBounds();
+            mSurfaceControlViewHost.relayout(bounds.width(), bounds.height());
+        }
+    }
+
+    /**
+     * Interface for returning screenshot outcome from calls to {@link #takeScreenshot}.
+     */
+    public interface ScreenshotCallback {
+
+        /**
+         * The status of a failed screenshot attempt provided by {@link #onFailure}.
+         *
+         * @hide
+         */
+        @IntDef(flag = false, prefix = {"ERROR_TAKE_SCREENSHOT_"}, value = {
+                ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, // 0
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface ScreenshotFailureStatus {
+        }
+
+        /**
+         * An error code indicating that an internal error occurred when attempting to take a
+         * screenshot of the game task. If this code is returned, the caller should verify that the
+         * conditions for taking a screenshot are met (device screen is on and the game task is
+         * visible). To do so, the caller can monitor the lifecycle methods for this session to
+         * make sure that the game task is focused. If the conditions are met, then the caller may
+         * try again immediately.
+         */
+        int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0;
+
+        /**
+         * Called when taking the screenshot failed.
+         * @param statusCode Indicates the reason for failure.
+         */
+        void onFailure(@ScreenshotFailureStatus int statusCode);
+
+        /**
+         * Called when taking the screenshot succeeded.
+         * @param bitmap The screenshot.
+         */
+        void onSuccess(@NonNull Bitmap bitmap);
+    }
+
+    /**
+     * Takes a screenshot of the associated game. For this call to succeed, the device screen
+     * must be turned on and the game task must be visible.
+     *
+     * If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link
+     * Bitmap} may be used.
+     *
+     * If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status
+     * code should be checked.
+     *
+     * If the status code is {@link ScreenshotCallback#ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR},
+     * then the caller should verify that the conditions for calling this method are met (device
+     * screen is on and the game task is visible). To do so, the caller can monitor the lifecycle
+     * methods for this session to make sure that the game task is focused. If the conditions are
+     * met, then the caller may try again immediately.
+     *
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when taking screenshot has succeeded
+     *                 or failed.
+     * @throws IllegalStateException if this method is called prior to {@link #onCreate}.
+     */
+    public void takeScreenshot(@NonNull Executor executor, @NonNull ScreenshotCallback callback) {
+        if (mGameSessionController == null) {
+            throw new IllegalStateException("Can not call before onCreate()");
+        }
+
+        AndroidFuture<GameScreenshotResult> takeScreenshotResult =
+                new AndroidFuture<GameScreenshotResult>().whenCompleteAsync((result, error) -> {
+                    handleScreenshotResult(callback, result, error);
+                }, executor);
+
+        try {
+            mGameSessionController.takeScreenshot(mTaskId, takeScreenshotResult);
+        } catch (RemoteException ex) {
+            takeScreenshotResult.completeExceptionally(ex);
+        }
+    }
+
+    private void handleScreenshotResult(
+            @NonNull ScreenshotCallback callback,
+            @NonNull GameScreenshotResult result,
+            @NonNull Throwable error) {
+        if (error != null) {
+            Slog.w(TAG, error.getMessage(), error.getCause());
+            callback.onFailure(
+                    ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR);
+            return;
+        }
+
+        @GameScreenshotResult.GameScreenshotStatus int status = result.getStatus();
+        switch (status) {
+            case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS:
+                callback.onSuccess(result.getBitmap());
+                break;
+            case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR:
+                Slog.w(TAG, "Error taking screenshot");
+                callback.onFailure(
+                        ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR);
+                break;
+        }
+    }
 }
diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java
index c1a3eb5..df5bad5 100644
--- a/core/java/android/service/games/GameSessionService.java
+++ b/core/java/android/service/games/GameSessionService.java
@@ -22,8 +22,12 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -48,8 +52,6 @@
  */
 @SystemApi
 public abstract class GameSessionService extends Service {
-    private static final String TAG = "GameSessionService";
-
     /**
      * The {@link Intent} action used when binding to the service.
      * To be supported, the service must require the
@@ -62,15 +64,28 @@
 
     private final IGameSessionService mInterface = new IGameSessionService.Stub() {
         @Override
-        public void create(CreateGameSessionRequest createGameSessionRequest,
+        public void create(
+                IGameSessionController gameSessionController,
+                CreateGameSessionRequest createGameSessionRequest,
+                GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
                 AndroidFuture gameSessionFuture) {
             Handler.getMain().post(PooledLambda.obtainRunnable(
                     GameSessionService::doCreate, GameSessionService.this,
+                    gameSessionController,
                     createGameSessionRequest,
+                    gameSessionViewHostConfiguration,
                     gameSessionFuture));
         }
     };
 
+    private DisplayManager mDisplayManager;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mDisplayManager = this.getSystemService(DisplayManager.class);
+    }
+
     @Override
     @Nullable
     public final IBinder onBind(@Nullable Intent intent) {
@@ -85,12 +100,39 @@
         return mInterface.asBinder();
     }
 
-    private void doCreate(CreateGameSessionRequest createGameSessionRequest,
-            AndroidFuture<IBinder> gameSessionFuture) {
+    private void doCreate(
+            IGameSessionController gameSessionController,
+            CreateGameSessionRequest createGameSessionRequest,
+            GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
+            AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) {
         GameSession gameSession = onNewSession(createGameSessionRequest);
         Objects.requireNonNull(gameSession);
 
-        gameSessionFuture.complete(gameSession.mInterface.asBinder());
+        Display display = mDisplayManager.getDisplay(gameSessionViewHostConfiguration.mDisplayId);
+        if (display == null) {
+            createGameSessionResultFuture.completeExceptionally(
+                    new IllegalStateException("No display found for id: "
+                            + gameSessionViewHostConfiguration.mDisplayId));
+            return;
+        }
+
+        IBinder hostToken = new Binder();
+        SurfaceControlViewHost surfaceControlViewHost =
+                new SurfaceControlViewHost(this, display, hostToken);
+
+        gameSession.attach(
+                gameSessionController,
+                createGameSessionRequest.getTaskId(),
+                this,
+                surfaceControlViewHost,
+                gameSessionViewHostConfiguration.mWidthPx,
+                gameSessionViewHostConfiguration.mHeightPx);
+
+        CreateGameSessionResult createGameSessionResult =
+                new CreateGameSessionResult(gameSession.mInterface,
+                        surfaceControlViewHost.getSurfacePackage());
+
+        createGameSessionResultFuture.complete(createGameSessionResult);
 
         gameSession.doCreate();
     }
diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.aidl b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl
new file mode 100644
index 0000000..b900b9d
--- /dev/null
+++ b/core/java/android/service/games/GameSessionViewHostConfiguration.aidl
@@ -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.
+ */
+
+package android.service.games;
+
+/**
+ * @hide
+ */
+parcelable GameSessionViewHostConfiguration;
diff --git a/core/java/android/service/games/GameSessionViewHostConfiguration.java b/core/java/android/service/games/GameSessionViewHostConfiguration.java
new file mode 100644
index 0000000..53db0df
--- /dev/null
+++ b/core/java/android/service/games/GameSessionViewHostConfiguration.java
@@ -0,0 +1,96 @@
+/*
+ * 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.games;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents the configuration of the {@link android.view.SurfaceControlViewHost} used to render
+ * the overlay for a game session.
+ *
+ * @hide
+ */
+@Hide
+public final class GameSessionViewHostConfiguration implements Parcelable {
+
+    @NonNull
+    public static final Creator<GameSessionViewHostConfiguration> CREATOR =
+            new Creator<GameSessionViewHostConfiguration>() {
+                @Override
+                public GameSessionViewHostConfiguration createFromParcel(Parcel source) {
+                    return new GameSessionViewHostConfiguration(
+                            source.readInt(),
+                            source.readInt(),
+                            source.readInt());
+                }
+
+                @Override
+                public GameSessionViewHostConfiguration[] newArray(int size) {
+                    return new GameSessionViewHostConfiguration[0];
+                }
+            };
+
+    final int mDisplayId;
+    final int mWidthPx;
+    final int mHeightPx;
+
+    public GameSessionViewHostConfiguration(int displayId, int widthPx, int heightPx) {
+        this.mDisplayId = displayId;
+        this.mWidthPx = widthPx;
+        this.mHeightPx = heightPx;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mDisplayId);
+        dest.writeInt(mWidthPx);
+        dest.writeInt(mHeightPx);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GameSessionViewHostConfiguration)) return false;
+        GameSessionViewHostConfiguration that = (GameSessionViewHostConfiguration) o;
+        return mDisplayId == that.mDisplayId && mWidthPx == that.mWidthPx
+                && mHeightPx == that.mHeightPx;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisplayId, mWidthPx, mHeightPx);
+    }
+
+    @Override
+    public String toString() {
+        return "GameSessionViewHostConfiguration{"
+                + "mDisplayId=" + mDisplayId
+                + ", mWidthPx=" + mWidthPx
+                + ", mHeightPx=" + mHeightPx
+                + '}';
+    }
+}
diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl
index b2e9f1d..71da630 100644
--- a/core/java/android/service/games/IGameSession.aidl
+++ b/core/java/android/service/games/IGameSession.aidl
@@ -20,5 +20,6 @@
  * @hide
  */
 oneway interface IGameSession {
-    void destroy();
+    void onDestroyed();
+    void onTaskFocusChanged(boolean focused);
 }
diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl
new file mode 100644
index 0000000..fe1d362
--- /dev/null
+++ b/core/java/android/service/games/IGameSessionController.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.games;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * @hide
+ */
+oneway interface IGameSessionController {
+    void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture);
+}
\ No newline at end of file
diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl
index 2a53ea7..37cde56 100644
--- a/core/java/android/service/games/IGameSessionService.aidl
+++ b/core/java/android/service/games/IGameSessionService.aidl
@@ -16,8 +16,10 @@
 
 package android.service.games;
 
+import android.service.games.IGameSessionController;
 import android.service.games.IGameSession;
 import android.service.games.CreateGameSessionRequest;
+import android.service.games.GameSessionViewHostConfiguration;
 
 import com.android.internal.infra.AndroidFuture;
 
@@ -27,6 +29,8 @@
  */
 oneway interface IGameSessionService {
     void create(
+            in IGameSessionController gameSessionController,
             in CreateGameSessionRequest createGameSessionRequest,
-            in AndroidFuture /* T=IBinder for IGameSession */ gameSessionFuture);
+            in GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
+            in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture);
 }
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 4f324f9..267b2ff 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -114,7 +114,7 @@
     }
 
     public Condition(Parcel source) {
-        this((Uri)source.readParcelable(Condition.class.getClassLoader()),
+        this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class),
                 source.readString(),
                 source.readString(),
                 source.readString(),
diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java
index 3d0984c..35b6bad 100644
--- a/core/java/android/service/notification/ConversationChannelWrapper.java
+++ b/core/java/android/service/notification/ConversationChannelWrapper.java
@@ -40,10 +40,10 @@
     public ConversationChannelWrapper() {}
 
     protected ConversationChannelWrapper(Parcel in) {
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mGroupLabel = in.readCharSequence();
         mParentChannelLabel = in.readCharSequence();
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
         mPkg = in.readStringNoHelper();
         mUid = in.readInt();
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c945954..ae39d3d 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1763,7 +1763,7 @@
             mImportanceExplanation = in.readCharSequence(); // may be null
             mRankingScore = in.readFloat();
             mOverrideGroupKey = in.readString(); // may be null
-            mChannel = in.readParcelable(cl); // may be null
+            mChannel = in.readParcelable(cl, android.app.NotificationChannel.class); // may be null
             mOverridePeople = in.createStringArrayList();
             mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
             mShowBadge = in.readBoolean();
@@ -1776,7 +1776,7 @@
             mCanBubble = in.readBoolean();
             mIsTextChanged = in.readBoolean();
             mIsConversation = in.readBoolean();
-            mShortcutInfo = in.readParcelable(cl);
+            mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
             mRankingAdjustment = in.readInt();
             mIsBubble = in.readBoolean();
         }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c64f4c46..a853714 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -30,7 +30,7 @@
     }
 
     public NotificationRankingUpdate(Parcel in) {
-        mRankingMap = in.readParcelable(getClass().getClassLoader());
+        mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class);
     }
 
     public NotificationListenerService.RankingMap getRankingMap() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index c1d5a28..8834cee 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -211,7 +211,7 @@
         allowCallsFrom = source.readInt();
         allowMessagesFrom = source.readInt();
         user = source.readInt();
-        manualRule = source.readParcelable(null);
+        manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class);
         final int len = source.readInt();
         if (len > 0) {
             final String[] ids = new String[len];
@@ -1800,10 +1800,10 @@
                 name = source.readString();
             }
             zenMode = source.readInt();
-            conditionId = source.readParcelable(null);
-            condition = source.readParcelable(null);
-            component = source.readParcelable(null);
-            configurationActivity = source.readParcelable(null);
+            conditionId = source.readParcelable(null, android.net.Uri.class);
+            condition = source.readParcelable(null, android.service.notification.Condition.class);
+            component = source.readParcelable(null, android.content.ComponentName.class);
+            configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
             if (source.readInt() == 1) {
                 id = source.readString();
             }
@@ -1811,7 +1811,7 @@
             if (source.readInt() == 1) {
                 enabler = source.readString();
             }
-            zenPolicy = source.readParcelable(null);
+            zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
             modified = source.readInt() == 1;
             pkg = source.readString();
         }
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index ed3a9ac..a04f073 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -804,8 +804,8 @@
         @Override
         public ZenPolicy createFromParcel(Parcel source) {
             ZenPolicy policy = new ZenPolicy();
-            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader());
-            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader());
+            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 8242f4e..44a8862 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -17,6 +17,7 @@
 package android.service.persistentdata;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -25,6 +26,8 @@
 import android.os.RemoteException;
 import android.service.oemlock.OemLockManager;
 
+import com.android.internal.R;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -50,6 +53,7 @@
 @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE)
 public class PersistentDataBlockManager {
     private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
+    private final Context mContext;
     private IPersistentDataBlockService sService;
 
     /**
@@ -74,7 +78,10 @@
     public @interface FlashLockState {}
 
     /** @hide */
-    public PersistentDataBlockManager(IPersistentDataBlockService service) {
+    public PersistentDataBlockManager(
+            Context context,
+            IPersistentDataBlockService service) {
+        mContext = context;
         sService = service;
     }
 
@@ -204,4 +211,15 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns the package name which can access the persistent data partition.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public String getPersistentDataPackageName() {
+        return mContext.getString(R.string.config_persistentDataPackageName);
+    }
 }
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
index 0551e27..7471a4f 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -63,7 +63,7 @@
     private static GetWalletCardsResponse readFromParcel(Parcel source) {
         int size = source.readInt();
         List<WalletCard> walletCards =
-                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader());
+                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader(), android.service.quickaccesswallet.WalletCard.class);
         int selectedIndex = source.readInt();
         return new GetWalletCardsResponse(walletCards, selectedIndex);
     }
diff --git a/core/java/android/service/security/attestationverification/OWNERS b/core/java/android/service/security/attestationverification/OWNERS
new file mode 100644
index 0000000..12c9978
--- /dev/null
+++ b/core/java/android/service/security/attestationverification/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/security/attestationverification/OWNERS
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/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index 3e63efb..16622d7 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -120,9 +120,9 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
-        mIcon = in.readParcelable(Icon.class.getClassLoader());
+        mIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
         mFlags = in.readInt();
-        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), android.app.PendingIntent.class);
     }
 
     public static final @android.annotation.NonNull Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
index 7dd85cc..b903fbe 100644
--- a/core/java/android/service/smartspace/SmartspaceService.java
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -245,7 +245,9 @@
     public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
 
     private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
-        Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+        if (DEBUG) {
+            Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+        }
         super.onDestroy();
         mSessionCallbacks.remove(sessionId);
         onDestroySmartspaceSession(sessionId);
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index 7005281..f6433b7 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -141,7 +141,7 @@
                     int type = in.readInt();
                     long creationElapsedMillis = in.readLong();
                     TimeZoneProviderSuggestion suggestion =
-                            in.readParcelable(getClass().getClassLoader());
+                            in.readParcelable(getClass().getClassLoader(), android.service.timezone.TimeZoneProviderSuggestion.class);
                     String failureCause = in.readString8();
                     return new TimeZoneProviderEvent(
                             type, creationElapsedMillis, suggestion, failureCause);
diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
index 229fa26..4841ac1 100644
--- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
+++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
@@ -100,7 +100,7 @@
                 public TimeZoneProviderSuggestion createFromParcel(Parcel in) {
                     @SuppressWarnings("unchecked")
                     ArrayList<String> timeZoneIds =
-                            (ArrayList<String>) in.readArrayList(null /* classLoader */);
+                            (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
                     long elapsedRealtimeMillis = in.readLong();
                     return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis);
                 }
diff --git a/core/java/android/service/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl
index 21661db..ec3b857 100644
--- a/core/java/android/service/trust/ITrustAgentService.aidl
+++ b/core/java/android/service/trust/ITrustAgentService.aidl
@@ -25,6 +25,7 @@
  */
 interface ITrustAgentService {
     oneway void onUnlockAttempt(boolean successful);
+    oneway void onUserRequestedUnlock();
     oneway void onUnlockLockout(int timeoutMs);
     oneway void onTrustTimeout();
     oneway void onDeviceLocked();
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 22ed1b8..fba61cf 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -186,6 +186,7 @@
     private static final int MSG_ESCROW_TOKEN_ADDED = 7;
     private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8;
     private static final int MSG_ESCROW_TOKEN_REMOVED = 9;
+    private static final int MSG_USER_REQUESTED_UNLOCK = 10;
 
     private static final String EXTRA_TOKEN = "token";
     private static final String EXTRA_TOKEN_HANDLE = "token_handle";
@@ -219,6 +220,9 @@
                 case MSG_UNLOCK_ATTEMPT:
                     onUnlockAttempt(msg.arg1 != 0);
                     break;
+                case MSG_USER_REQUESTED_UNLOCK:
+                    onUserRequestedUnlock();
+                    break;
                 case MSG_UNLOCK_LOCKOUT:
                     onDeviceUnlockLockout(msg.arg1);
                     break;
@@ -306,7 +310,7 @@
      *
      * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE
      *
-     * TODO(b/213631672): Hook up call from system server & SystemUI, then un-hide
+     * TODO(b/213631672): Add CTS tests
      * @hide
      */
     public void onUserRequestedUnlock() {
@@ -665,6 +669,11 @@
         }
 
         @Override
+        public void onUserRequestedUnlock() {
+            mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK).sendToTarget();
+        }
+
+        @Override
         public void onUnlockLockout(int timeoutMs) {
             mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget();
         }
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 81af6a2..93d4def 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -46,4 +46,5 @@
     oneway void removeLocalColorsAreas(in List<RectF> regions);
     oneway void addLocalColorsAreas(in List<RectF> regions);
     SurfaceControl mirrorSurfaceControl();
+    oneway void applyDimming(float dimAmount);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 73ffd66..f2a0355 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -26,6 +26,7 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import android.animation.ValueAnimator;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -86,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;
@@ -159,6 +161,7 @@
     private static final int MSG_ZOOM = 10100;
     private static final int MSG_SCALE_PREVIEW = 10110;
     private static final int MSG_REPORT_SHOWN = 10150;
+    private static final int MSG_UPDATE_DIMMING = 10200;
     private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
             Float.NEGATIVE_INFINITY);
 
@@ -167,6 +170,8 @@
     private static final boolean ENABLE_WALLPAPER_DIMMING =
             SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true);
 
+    private static final long DIMMING_ANIMATION_DURATION_MS = 300L;
+
     private final ArrayList<Engine> mActiveEngines
             = new ArrayList<Engine>();
 
@@ -221,6 +226,9 @@
         boolean mOffsetsChanged;
         boolean mFixedSizeAllowed;
         boolean mShouldDim;
+        // Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
+        // based on its color hints
+        boolean mShouldDimByDefault;
         int mWidth;
         int mHeight;
         int mFormat;
@@ -271,7 +279,10 @@
         private Display mDisplay;
         private Context mDisplayContext;
         private int mDisplayState;
+        private @Surface.Rotation int mDisplayInstallOrientation;
         private float mWallpaperDimAmount = 0.05f;
+        private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+        private float mDefaultDimAmount = mWallpaperDimAmount;
 
         SurfaceControl mSurfaceControl = new SurfaceControl();
         SurfaceControl mBbqSurfaceControl;
@@ -379,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);
             }
 
@@ -861,15 +873,34 @@
                 return;
             }
             int colorHints = colors.getColorHints();
-            boolean shouldDim = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
+            mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
                     && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
-            if (shouldDim != mShouldDim) {
-                mShouldDim = shouldDim;
+
+            // If default dimming value changes and no additional dimming is applied
+            if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
+                mShouldDim = mShouldDimByDefault;
                 updateSurfaceDimming();
                 updateSurface(false, false, true);
             }
         }
 
+        /**
+         * Update the dim amount of the wallpaper by updating the surface.
+         *
+         * @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
+         */
+        private void updateWallpaperDimming(float dimAmount) {
+            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+
+            // Custom dim amount cannot be less than the default dim amount.
+            mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
+            // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
+            // based on its default wallpaper color hints.
+            mShouldDim = dimAmount != 0f || mShouldDimByDefault;
+            updateSurfaceDimming();
+            updateSurface(false, false, true);
+        }
+
         private void updateSurfaceDimming() {
             if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
                 return;
@@ -878,9 +909,21 @@
             // preview mode.
             if (!isPreview() && mShouldDim) {
                 Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
-                new SurfaceControl.Transaction()
-                        .setAlpha(mBbqSurfaceControl, 1 - mWallpaperDimAmount)
-                        .apply();
+                SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction();
+
+                // Animate dimming to gradually change the wallpaper alpha from the previous
+                // dim amount to the new amount only if the dim amount changed.
+                ValueAnimator animator = ValueAnimator.ofFloat(
+                        mPreviousWallpaperDimAmount, mWallpaperDimAmount);
+                animator.setDuration(mPreviousWallpaperDimAmount == mWallpaperDimAmount
+                        ? 0 : DIMMING_ANIMATION_DURATION_MS);
+                animator.addUpdateListener((ValueAnimator va) -> {
+                    final float dimValue = (float) va.getAnimatedValue();
+                    surfaceControl
+                            .setAlpha(mBbqSurfaceControl, 1 - dimValue)
+                            .apply();
+                });
+                animator.start();
             } else {
                 Log.v(TAG, "Setting wallpaper dimming: " + 0);
                 new SurfaceControl.Transaction()
@@ -987,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");
@@ -1025,8 +1072,6 @@
                     mLayout.x = 0;
                     mLayout.y = 0;
 
-                    mLayout.width = myWidth;
-                    mLayout.height = myHeight;
                     mLayout.format = mFormat;
 
                     mCurWindowFlags = mWindowFlags;
@@ -1035,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;
 
@@ -1080,8 +1142,15 @@
 
                     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) {
                             mBbqSurfaceControl = new SurfaceControl.Builder()
@@ -1095,9 +1164,9 @@
                                     .build();
                             updateSurfaceDimming();
                         }
-                        // Propagate transform hint from WM so we can use the right hint for the
+                        // Propagate transform hint from WM, so we can use the right hint for the
                         // first frame.
-                        mBbqSurfaceControl.setTransformHint(mSurfaceControl.getTransformHint());
+                        mBbqSurfaceControl.setTransformHint(transformHint);
                         Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
                                 mSurfaceSize.y, mFormat);
                         // If blastSurface == null that means it hasn't changed since the last
@@ -1118,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,
@@ -1332,9 +1400,12 @@
             // Use window context of TYPE_WALLPAPER so client can access UI resources correctly.
             mDisplayContext = createDisplayContext(mDisplay)
                     .createWindowContext(TYPE_WALLPAPER, null /* options */);
-            mWallpaperDimAmount = mDisplayContext.getResources().getFloat(
+            mDefaultDimAmount = mDisplayContext.getResources().getFloat(
                     com.android.internal.R.dimen.config_wallpaperDimAmount);
+            mWallpaperDimAmount = mDefaultDimAmount;
+            mPreviousWallpaperDimAmount = mWallpaperDimAmount;
             mDisplayState = mDisplay.getState();
+            mDisplayInstallOrientation = mDisplay.getInstallOrientation();
 
             if (DEBUG) Log.v(TAG, "onCreate(): " + this);
             onCreate(mSurfaceHolder);
@@ -1587,6 +1658,7 @@
                 return;
             }
             Surface surface = mSurfaceHolder.getSurface();
+            if (!surface.isValid()) return;
             boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
             int smaller = widthIsLarger ? mSurfaceSize.x
                     : mSurfaceSize.y;
@@ -1647,7 +1719,7 @@
                     Log.e(TAG, "Error creating page local color bitmap", e);
                     continue;
                 }
-                WallpaperColors color = WallpaperColors.fromBitmap(target);
+                WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount);
                 target.recycle();
                 WallpaperColors currentColor = page.getColors(area);
 
@@ -2175,6 +2247,12 @@
             mDetached.set(true);
         }
 
+        public void applyDimming(float dimAmount) throws RemoteException {
+            Message msg = mCaller.obtainMessageI(MSG_UPDATE_DIMMING,
+                    Float.floatToIntBits(dimAmount));
+            mCaller.sendMessage(msg);
+        }
+
         public void scalePreview(Rect position) {
             Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position);
             mCaller.sendMessage(msg);
@@ -2245,6 +2323,9 @@
                 case MSG_ZOOM:
                     mEngine.setZoom(Float.intBitsToFloat(message.arg1));
                     break;
+                case MSG_UPDATE_DIMMING:
+                    mEngine.updateWallpaperDimming(Float.intBitsToFloat(message.arg1));
+                    break;
                 case MSG_SCALE_PREVIEW:
                     mEngine.scalePreview((Rect) message.obj);
                     break;
@@ -2262,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/service/wallpapereffectsgeneration/OWNERS b/core/java/android/service/wallpapereffectsgeneration/OWNERS
new file mode 100644
index 0000000..d2d3e2c0
--- /dev/null
+++ b/core/java/android/service/wallpapereffectsgeneration/OWNERS
@@ -0,0 +1,4 @@
+susharon@google.com
+shanh@google.com
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java
index 7ffe5eb..0d98a6c 100644
--- a/core/java/android/speech/tts/Voice.java
+++ b/core/java/android/speech/tts/Voice.java
@@ -84,7 +84,7 @@
 
     private Voice(Parcel in) {
         this.mName = in.readString();
-        this.mLocale = (Locale)in.readSerializable();
+        this.mLocale = (Locale)in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
         this.mQuality = in.readInt();
         this.mLatency = in.readInt();
         this.mRequiresNetworkConnection = (in.readByte() == 1);
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index d5ac436..fb2d771 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -99,7 +99,7 @@
     }
 
     private SubscriptionPlan(Parcel source) {
-        cycleRule = source.readParcelable(null);
+        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
         title = source.readCharSequence();
         summary = source.readCharSequence();
         dataLimitBytes = source.readLong();
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 2f7fb2f..32b3bc6 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -143,9 +143,9 @@
         @Override
         public FontConfig createFromParcel(Parcel source) {
             List<FontFamily> families = source.readParcelableList(new ArrayList<>(),
-                    FontFamily.class.getClassLoader());
+                    FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class);
             List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
-                    Alias.class.getClassLoader());
+                    Alias.class.getClassLoader(), android.text.FontConfig.Alias.class);
             long lastModifiedDate = source.readLong();
             int configVersion = source.readInt();
             return new FontConfig(families, aliases, lastModifiedDate, configVersion);
@@ -617,7 +617,7 @@
             @Override
             public FontFamily createFromParcel(Parcel source) {
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader());
+                        new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class);
                 String name = source.readString8();
                 String langTags = source.readString8();
                 int variant = source.readInt();
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/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index ccccdcf..3da8333 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -82,7 +82,7 @@
      * Constructor called from {@link TextUtils} to restore the span.
      */
     public EasyEditSpan(@NonNull Parcel source) {
-        mPendingIntent = source.readParcelable(null);
+        mPendingIntent = source.readParcelable(null, android.app.PendingIntent.class);
         mDeleteEnabled = (source.readByte() == 1);
     }
 
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 2355769..adb379a 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -249,7 +249,7 @@
         mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
 
         mTextFontWeight = src.readInt();
-        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader());
+        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
 
         mShadowRadius = src.readFloat();
         mShadowDx = src.readFloat();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 52f1fae..96a1fc6 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -81,6 +81,7 @@
         DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
+        DEFAULT_FLAGS.put("settings_search_always_expand", "false");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
     }
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 42181c3..5cbbbef 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -80,7 +80,7 @@
 
     private MemoryIntArray(Parcel parcel) throws IOException {
         mIsOwner = false;
-        ParcelFileDescriptor pfd = parcel.readParcelable(null);
+        ParcelFileDescriptor pfd = parcel.readParcelable(null, android.os.ParcelFileDescriptor.class);
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java
index dc93a47..e8d96d8 100644
--- a/core/java/android/util/SparseDoubleArray.java
+++ b/core/java/android/util/SparseDoubleArray.java
@@ -81,9 +81,17 @@
      * if no such mapping has been made.
      */
     public double get(int key) {
+        return get(key, 0);
+    }
+
+    /**
+     * Gets the double mapped from the specified key, or the specified value
+     * if no such mapping has been made.
+     */
+    public double get(int key, double valueIfKeyNotFound) {
         final int index = mValues.indexOfKey(key);
         if (index < 0) {
-            return 0.0d;
+            return valueIfKeyNotFound;
         }
         return valueAt(index);
     }
@@ -105,7 +113,7 @@
      * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
      * (in the numerical sense) to it.
      */
-    public void add(int key, double summand) {
+    public void incrementValue(int key, double summand) {
         final double oldValue = get(key);
         put(key, oldValue + summand);
     }
@@ -138,6 +146,13 @@
     }
 
     /**
+     * Removes all key-value mappings from this SparseDoubleArray.
+     */
+    public void clear() {
+        mValues.clear();
+    }
+
+    /**
      * {@inheritDoc}
      *
      * <p>This implementation composes a string by iterating over its mappings.
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index f2bc0c5..7185972 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -164,6 +164,30 @@
     }
 
     /**
+     * Adds a mapping from the specified key to the specified value,
+     * <b>adding</b> its value to the previous mapping from the specified key if there
+     * was one.
+     *
+     * <p>This differs from {@link #put} because instead of replacing any previous value, it adds
+     * (in the numerical sense) to it.
+     *
+     * @hide
+     */
+    public void incrementValue(int key, long summand) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] += summand;
+        } else {
+            i = ~i;
+
+            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+            mValues = GrowingArrayUtils.insert(mValues, mSize, i, summand);
+            mSize++;
+        }
+    }
+
+    /**
      * Returns the number of key-value mappings that this SparseLongArray
      * currently stores.
      */
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/Choreographer.java b/core/java/android/view/Choreographer.java
index 9b8523f..b8eb602 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -779,7 +779,7 @@
                                 + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                     }
                     frameTimeNanos = startNanos - lastFrameOffset;
-                    frameData.setFrameTimeNanos(-lastFrameOffset);
+                    frameData.setFrameTimeNanos(frameTimeNanos);
                 }
 
                 if (frameTimeNanos < mLastFrameTimeNanos) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 70266c1..d6e074f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -928,6 +928,18 @@
     }
 
     /**
+     * Returns the install orientation of the display.
+     * @hide
+     */
+    @Surface.Rotation
+    public int getInstallOrientation() {
+        synchronized (mLock) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.installOrientation;
+        }
+    }
+
+    /**
      * @deprecated use {@link #getRotation}
      * @return orientation of this display.
      */
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/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index b8614cc..6917d66 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -318,6 +318,12 @@
     @Nullable
     public RoundedCorners roundedCorners;
 
+    /**
+     * Install orientation of the display relative to its natural orientation.
+     */
+    @Surface.Rotation
+    public int installOrientation;
+
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
         @Override
         public DisplayInfo createFromParcel(Parcel source) {
@@ -389,7 +395,8 @@
                 && brightnessMaximum == other.brightnessMaximum
                 && brightnessDefault == other.brightnessDefault
                 && Objects.equals(roundedCorners, other.roundedCorners)
-                && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher;
+                && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher
+                && installOrientation == other.installOrientation;
     }
 
     @Override
@@ -441,6 +448,7 @@
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
         shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher;
+        installOrientation = other.installOrientation;
     }
 
     public void readFromParcel(Parcel source) {
@@ -449,8 +457,8 @@
         type = source.readInt();
         displayId = source.readInt();
         displayGroupId = source.readInt();
-        address = source.readParcelable(null);
-        deviceProductInfo = source.readParcelable(null);
+        address = source.readParcelable(null, android.view.DisplayAddress.class);
+        deviceProductInfo = source.readParcelable(null, android.hardware.display.DeviceProductInfo.class);
         name = source.readString8();
         appWidth = source.readInt();
         appHeight = source.readInt();
@@ -475,7 +483,7 @@
         for (int i = 0; i < nColorModes; i++) {
             supportedColorModes[i] = source.readInt();
         }
-        hdrCapabilities = source.readParcelable(null);
+        hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
         minimalPostProcessingSupported = source.readBoolean();
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
@@ -498,6 +506,7 @@
             userDisabledHdrTypes[i] = source.readInt();
         }
         shouldConstrainMetricsForLauncher = source.readBoolean();
+        installOrientation = source.readInt();
     }
 
     @Override
@@ -553,6 +562,7 @@
             dest.writeInt(userDisabledHdrTypes[i]);
         }
         dest.writeBoolean(shouldConstrainMetricsForLauncher);
+        dest.writeInt(installOrientation);
     }
 
     @Override
@@ -809,6 +819,8 @@
         sb.append(brightnessDefault);
         sb.append(", shouldConstrainMetricsForLauncher ");
         sb.append(shouldConstrainMetricsForLauncher);
+        sb.append(", installOrientation ");
+        sb.append(Surface.rotationToString(installOrientation));
         sb.append("}");
         return sb.toString();
     }
diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java
deleted file mode 100644
index fffb323e..0000000
--- a/core/java/android/view/GestureExclusionTracker.java
+++ /dev/null
@@ -1,142 +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 android.view;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views.
- */
-class GestureExclusionTracker {
-    private boolean mGestureExclusionViewsChanged = false;
-    private boolean mRootGestureExclusionRectsChanged = false;
-    private List<Rect> mRootGestureExclusionRects = Collections.emptyList();
-    private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>();
-    private List<Rect> mGestureExclusionRects = Collections.emptyList();
-
-    public void updateRectsForView(@NonNull View view) {
-        boolean found = false;
-        final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
-        while (i.hasNext()) {
-            final GestureExclusionViewInfo info = i.next();
-            final View v = info.getView();
-            if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
-                mGestureExclusionViewsChanged = true;
-                i.remove();
-                continue;
-            }
-            if (v == view) {
-                found = true;
-                info.mDirty = true;
-                break;
-            }
-        }
-        if (!found && view.isAttachedToWindow()) {
-            mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view));
-            mGestureExclusionViewsChanged = true;
-        }
-    }
-
-    @Nullable
-    public List<Rect> computeChangedRects() {
-        boolean changed = mRootGestureExclusionRectsChanged;
-        final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator();
-        final List<Rect> rects = new ArrayList<>(mRootGestureExclusionRects);
-        while (i.hasNext()) {
-            final GestureExclusionViewInfo info = i.next();
-            switch (info.update()) {
-                case GestureExclusionViewInfo.CHANGED:
-                    changed = true;
-                    // Deliberate fall-through
-                case GestureExclusionViewInfo.UNCHANGED:
-                    rects.addAll(info.mExclusionRects);
-                    break;
-                case GestureExclusionViewInfo.GONE:
-                    mGestureExclusionViewsChanged = true;
-                    i.remove();
-                    break;
-            }
-        }
-        if (changed || mGestureExclusionViewsChanged) {
-            mGestureExclusionViewsChanged = false;
-            mRootGestureExclusionRectsChanged = false;
-            if (!mGestureExclusionRects.equals(rects)) {
-                mGestureExclusionRects = rects;
-                return rects;
-            }
-        }
-        return null;
-    }
-
-    public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) {
-        Preconditions.checkNotNull(rects, "rects must not be null");
-        mRootGestureExclusionRects = rects;
-        mRootGestureExclusionRectsChanged = true;
-    }
-
-    @NonNull
-    public List<Rect> getRootSystemGestureExclusionRects() {
-        return mRootGestureExclusionRects;
-    }
-
-    private static class GestureExclusionViewInfo {
-        public static final int CHANGED = 0;
-        public static final int UNCHANGED = 1;
-        public static final int GONE = 2;
-
-        private final WeakReference<View> mView;
-        boolean mDirty = true;
-        List<Rect> mExclusionRects = Collections.emptyList();
-
-        GestureExclusionViewInfo(View view) {
-            mView = new WeakReference<>(view);
-        }
-
-        public View getView() {
-            return mView.get();
-        }
-
-        public int update() {
-            final View excludedView = getView();
-            if (excludedView == null || !excludedView.isAttachedToWindow()
-                    || !excludedView.isAggregatedVisible()) return GONE;
-            final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
-            final List<Rect> newRects = new ArrayList<>(localRects.size());
-            for (Rect src : localRects) {
-                Rect mappedRect = new Rect(src);
-                ViewParent p = excludedView.getParent();
-                if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) {
-                    newRects.add(mappedRect);
-                }
-            }
-
-            if (mExclusionRects.equals(localRects)) return UNCHANGED;
-            mExclusionRects = newRects;
-            return CHANGED;
-        }
-    }
-}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 8524ac84..097d1d0 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -75,11 +74,6 @@
     @VisibleForTesting
     public WeakReference<View> mConnectedView = null;
 
-    /** The editor bound reported by the connected View. */
-    @Nullable
-    @VisibleForTesting
-    public Rect mEditorBound = null;
-
     /**
      * When InputConnection restarts for a View, View#onInputConnectionCreatedInternal
      * might be called before View#onInputConnectionClosedInternal, so we need to count the input
@@ -174,9 +168,8 @@
      * @param view the view that created the current InputConnection.
      * @see  #onInputConnectionClosed(View)
      */
-    public void onInputConnectionCreated(@NonNull View view, @NonNull EditorInfo editorInfo) {
+    public void onInputConnectionCreated(@NonNull View view) {
         final View connectedView = getConnectedView();
-//        updateEditorBound(editorInfo.getInitialEditorBound());
         if (connectedView == view) {
             ++mConnectionCount;
         } else {
@@ -198,33 +191,15 @@
             --mConnectionCount;
             if (mConnectionCount == 0) {
                 mConnectedView = null;
-                mEditorBound = null;
             }
         } else {
             // Unexpected branch, set mConnectedView to null to avoid further problem.
             mConnectedView = null;
-            mEditorBound = null;
             mConnectionCount = 0;
         }
     }
 
     /**
-     * Notify the HandwritingInitiator that editor bound of the connected view(the view with
-     * active InputConnection) has be updated.
-     * @param editorBound new the editor bounds of the connected view.
-     */
-    public void updateEditorBound(@NonNull Rect editorBound) {
-        if (mEditorBound == null) {
-            mEditorBound = new Rect(editorBound);
-        } else {
-            mEditorBound.left = editorBound.left;
-            mEditorBound.top = editorBound.top;
-            mEditorBound.right = editorBound.right;
-            mEditorBound.bottom = editorBound.bottom;
-        }
-    }
-
-    /**
      * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
      * the following 3 conditions should meet:
      *   a) The stylus movement exceeds the touchSlop.
@@ -240,18 +215,19 @@
             return;
         }
         final View connectedView = getConnectedView();
-        if (connectedView == null || mEditorBound == null) {
+        if (connectedView == null) {
             return;
         }
         final ViewParent viewParent = connectedView.getParent();
         // Do a final check before startHandwriting.
         if (viewParent != null && connectedView.isAttachedToWindow()) {
-            final Rect editorBounds = new Rect(mEditorBound);
+            final Rect editorBounds =
+                    new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight());
             if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) {
                 final int roundedInitX = Math.round(mState.mStylusDownX);
                 final int roundedInitY = Math.round(mState.mStylusDownY);
                 if (editorBounds.contains(roundedInitX, roundedInitY)) {
-                    startHandwriting(mConnectedView.get());
+                    startHandwriting(connectedView);
                 }
             }
         }
@@ -261,7 +237,7 @@
     /** For test only. */
     @VisibleForTesting
     public void startHandwriting(View view) {
-        // mImm.startHandwriting(view);
+        mImm.startStylusHandwriting(view);
     }
 
     private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) {
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index f95d6b3..449e9b3 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -16,8 +16,11 @@
 
 package android.view;
 
+import android.graphics.Rect;
 import android.content.res.Configuration;
 
+import java.util.List;
+
 /**
  * Interface to listen for changes to display window-containers.
  *
@@ -56,4 +59,9 @@
      * Called when the previous fixed rotation on a display is finished.
      */
     void onFixedRotationFinished(int displayId);
+
+    /**
+     * Called when the keep clear ares on a display have changed.
+     */
+    void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2c766bd..c5ccc18 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -25,7 +25,6 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
-import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -66,6 +65,7 @@
 import android.view.SurfaceControl;
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
+import android.window.IOnFpsCallbackListener;
 
 /**
  * System private interface to the window manager.
@@ -484,16 +484,6 @@
     void getStableInsets(int displayId, out Rect outInsets);
 
     /**
-     * Set the forwarded insets on the display.
-     * <p>
-     * This is only used in case a virtual display is displayed on another display that has insets,
-     * and the bounds of the virtual display is overlapping with the insets from the host display.
-     * In that case, the contents on the virtual display won't be placed over the forwarded insets.
-     * Only the owner of the display is permitted to set the forwarded insets on it.
-     */
-    void setForwardedInsets(int displayId, in Insets insets);
-
-    /**
      * Register shortcut key. Shortcut code is packed as:
      * (MetaState << Integer.SIZE) | KeyCode
      * @hide
@@ -551,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();
@@ -922,4 +917,28 @@
      * reverts to using the default task transition with no spec changes.
      */
     void clearTaskTransitionSpec();
+
+    /**
+     * Registers the frame rate per second count callback for one given task ID.
+     * Each callback can only register for receiving FPS callback for one task id until unregister
+     * is called. If there's no task associated with the given task id,
+     * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is
+     * registered, the registered callback will not be unregistered until
+     * {@link unregisterTaskFpsCallback()} is called
+     * @param taskId task id of the task.
+     * @param listener listener to be registered.
+     *
+     * @hide
+     */
+    void registerTaskFpsCallback(in int taskId, in IOnFpsCallbackListener listener);
+
+    /**
+     * Unregisters the frame rate per second count callback which was registered with
+     * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}.
+     *
+     * @param listener listener to be unregistered.
+     *
+     * @hide
+     */
+    void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 921ce53..32054b1 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -37,6 +37,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.window.ClientWindowFrames;
+import android.window.IOnBackInvokedCallback;
 
 import java.util.List;
 
@@ -73,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
@@ -96,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
@@ -290,12 +288,17 @@
     oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects);
 
     /**
+     * Called when the keep-clear areas for this window have changed.
+     */
+    oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects);
+
+    /**
     * Request the server to call setInputWindowInfo on a given Surface, and return
     * an input channel where the client can receive input.
     */
     void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
             in IBinder hostInputToken, int flags, int privateFlags, int type,
-            out InputChannel outInputChannel);
+            in IBinder focusGrantToken, out InputChannel outInputChannel);
 
     /**
      * Update the flags on an input channel associated with a particular surface.
@@ -328,4 +331,17 @@
      */
     oneway void generateDisplayHash(IWindow window, in Rect boundsInWindow,
             in String hashAlgorithm, in RemoteCallback callback);
+
+    /**
+     * Sets the {@link IOnBackInvokedCallback} to be invoked for a window when back is triggered.
+     *
+     * @param window The token for the window to set the callback to.
+     * @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/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 2660e74..118b03c 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -91,7 +91,7 @@
 
     private KeyboardShortcutInfo(Parcel source) {
         mLabel = source.readCharSequence();
-        mIcon = source.readParcelable(null);
+        mIcon = source.readParcelable(null, android.graphics.drawable.Icon.class);
         mBaseCharacter = (char) source.readInt();
         mKeycode = source.readInt();
         mModifiers = source.readInt();
diff --git a/core/java/android/view/OnBackInvokedCallback.java b/core/java/android/view/OnBackInvokedCallback.java
new file mode 100644
index 0000000..b5cd89c
--- /dev/null
+++ b/core/java/android/view/OnBackInvokedCallback.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 android.view;
+
+import android.app.Activity;
+import android.app.Dialog;
+
+/**
+ * Interface for applications to register back invocation callbacks. This allows the client
+ * to customize various back behaviors by overriding the corresponding callback methods.
+ *
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
+ * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
+ * {@link Dialog} and {@link View}).
+ *
+ * Under the hood callbacks are registered at window level. When back is triggered,
+ * callbacks on the in-focus window are invoked in reverse order in which they are added
+ * within the same priority. Between different pirorities, callbacks with higher priority
+ * are invoked first.
+ *
+ * See {@link OnBackInvokedDispatcher#registerOnBackInvokedCallback(OnBackInvokedCallback, int)}
+ * for specifying callback priority.
+ */
+public interface OnBackInvokedCallback {
+   /**
+    * Called when a back gesture has been started, or back button has been pressed down.
+    *
+    * @hide
+    */
+    default void onBackStarted() { };
+
+    /**
+     * Called on back gesture progress.
+     *
+     * @param touchX Absolute X location of the touch point.
+     * @param touchY Absolute Y location of the touch point.
+     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     *
+     * @hide
+     */
+    // TODO(b/210539672): combine back progress params into BackEvent.
+    default void onBackProgressed(int touchX, int touchY, float progress) { };
+
+    /**
+     * Called when a back gesture or back button press has been cancelled.
+     *
+     * @hide
+     */
+    default void onBackCancelled() { };
+
+    /**
+     * Called when a back gesture has been completed and committed, or back button pressed
+     * has been released and committed.
+     */
+    default void onBackInvoked() { };
+}
diff --git a/core/java/android/view/OnBackInvokedDispatcher.java b/core/java/android/view/OnBackInvokedDispatcher.java
new file mode 100644
index 0000000..05c312b
--- /dev/null
+++ b/core/java/android/view/OnBackInvokedDispatcher.java
@@ -0,0 +1,76 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Dispatcher to register {@link OnBackInvokedCallback} instances for handling
+ * back invocations.
+ *
+ * It also provides interfaces to update the attributes of {@link OnBackInvokedCallback}.
+ * Attribute updates are proactively pushed to the window manager if they change the dispatch
+ * target (a.k.a. the callback to be invoked next), or its behavior.
+ */
+public abstract class OnBackInvokedDispatcher {
+    /** @hide */
+    @IntDef({
+            PRIORITY_DEFAULT,
+            PRIORITY_OVERLAY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Priority{}
+
+    /**
+     * Priority level of {@link OnBackInvokedCallback}s for overlays such as menus and
+     * navigation drawers that should receive back dispatch before non-overlays.
+     */
+    public static final int PRIORITY_OVERLAY = 1000000;
+
+    /**
+     * Default priority level of {@link OnBackInvokedCallback}s.
+     */
+    public static final int PRIORITY_DEFAULT = 0;
+
+    /**
+     * Registers a {@link OnBackInvokedCallback}.
+     *
+     * Within the same priority level, callbacks are invoked in the reverse order in which
+     * they are registered. Higher priority callbacks are invoked before lower priority ones.
+     *
+     * @param callback The callback to be registered. If the callback instance has been already
+     *                 registered, the existing instance (no matter its priority) will be
+     *                 unregistered and registered again.
+     * @param priority The priority of the callback.
+     */
+    @SuppressLint("SamShouldBeLast")
+    public abstract void registerOnBackInvokedCallback(
+            @NonNull OnBackInvokedCallback callback, @Priority int priority);
+
+    /**
+     * Unregisters a {@link OnBackInvokedCallback}.
+     *
+     * @param callback The callback to be unregistered. Does nothing if the callback has not been
+     *                 registered.
+     */
+    public abstract void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback);
+}
diff --git a/core/java/android/view/OnBackInvokedDispatcherOwner.java b/core/java/android/view/OnBackInvokedDispatcherOwner.java
new file mode 100644
index 0000000..0e14ed4
--- /dev/null
+++ b/core/java/android/view/OnBackInvokedDispatcherOwner.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 android.view;
+
+import android.annotation.Nullable;
+
+/**
+ * A class that provides an {@link OnBackInvokedDispatcher} that allows you to register
+ * an {@link OnBackInvokedCallback} for handling the system back invocation behavior.
+ */
+public interface OnBackInvokedDispatcherOwner {
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} that should dispatch the back invocation
+     * to its registered {@link OnBackInvokedCallback}s.
+     * Returns null when the root view is not attached to a window or a view tree with a decor.
+     */
+    @Nullable
+    OnBackInvokedDispatcher getOnBackInvokedDispatcher();
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b7f9be7..e751720b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1831,13 +1831,15 @@
         public float density;
         public boolean secure;
         public DeviceProductInfo deviceProductInfo;
+        public @Surface.Rotation int installOrientation;
 
         @Override
         public String toString() {
             return "StaticDisplayInfo{isInternal=" + isInternal
                     + ", density=" + density
                     + ", secure=" + secure
-                    + ", deviceProductInfo=" + deviceProductInfo + "}";
+                    + ", deviceProductInfo=" + deviceProductInfo
+                    + ", installOrientation=" + installOrientation + "}";
         }
 
         @Override
@@ -1848,12 +1850,13 @@
             return isInternal == that.isInternal
                     && density == that.density
                     && secure == that.secure
-                    && Objects.equals(deviceProductInfo, that.deviceProductInfo);
+                    && Objects.equals(deviceProductInfo, that.deviceProductInfo)
+                    && installOrientation == that.installOrientation;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(isInternal, density, secure, deviceProductInfo);
+            return Objects.hash(isInternal, density, secure, deviceProductInfo, installOrientation);
         }
     }
 
diff --git a/core/java/android/view/SurfaceControlFpsListener.java b/core/java/android/view/SurfaceControlFpsListener.java
deleted file mode 100644
index 20a511a..0000000
--- a/core/java/android/view/SurfaceControlFpsListener.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 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.view;
-
-import android.annotation.NonNull;
-
-/**
- * Listener for sampling the frames per second for a SurfaceControl and its children.
- * This should only be used by a system component that needs to listen to a SurfaceControl's
- * tree's FPS when it is not actively submitting transactions for that SurfaceControl.
- * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used.
- *
- * @hide
- */
-public abstract class SurfaceControlFpsListener {
-    private long mNativeListener;
-
-    public SurfaceControlFpsListener() {
-        mNativeListener = nativeCreate(this);
-    }
-
-    protected void destroy() {
-        if (mNativeListener == 0) {
-            return;
-        }
-        unregister();
-        nativeDestroy(mNativeListener);
-        mNativeListener = 0;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            destroy();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    /**
-     * Reports the fps from the registered SurfaceControl
-     */
-    public abstract void onFpsReported(float fps);
-
-    /**
-     * Registers the sampling listener for a particular task ID
-     */
-    public void register(int taskId) {
-        if (mNativeListener == 0) {
-            return;
-        }
-
-        nativeRegister(mNativeListener, taskId);
-    }
-
-    /**
-     * Unregisters the sampling listener.
-     */
-    public void unregister() {
-        if (mNativeListener == 0) {
-            return;
-        }
-        nativeUnregister(mNativeListener);
-    }
-
-    /**
-     * Dispatch the collected sample.
-     *
-     * Called from native code on a binder thread.
-     */
-    private static void dispatchOnFpsReported(
-            @NonNull SurfaceControlFpsListener listener, float fps) {
-        listener.onFpsReported(fps);
-    }
-
-    private static native long nativeCreate(SurfaceControlFpsListener thiz);
-    private static native void nativeDestroy(long ptr);
-    private static native void nativeRegister(long ptr, int taskId);
-    private static native void nativeUnregister(long ptr);
-}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 85a9dbd..22baa69 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -274,7 +274,7 @@
     public @Nullable SurfacePackage getSurfacePackage() {
         if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
             return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection,
-                mViewRoot.getInputToken(), mRemoteInterface);
+                mWm.getFocusGrantToken(), mRemoteInterface);
         } else {
             return null;
         }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 41a31e1..93fdee0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -834,7 +834,7 @@
  */
 @UiThread
 public class View implements Drawable.Callback, KeyEvent.Callback,
-        AccessibilityEventSource {
+        AccessibilityEventSource, OnBackInvokedDispatcherOwner {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private static final boolean DBG = false;
 
@@ -4734,15 +4734,18 @@
         WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback;
 
         /**
-         * This lives here since it's only valid for interactive views. This list is null until the
-         * first use.
+         * This lives here since it's only valid for interactive views. This list is null
+         * until its first use.
          */
         private List<Rect> mSystemGestureExclusionRects = null;
+        private List<Rect> mKeepClearRects = null;
+        private boolean mPreferKeepClear = false;
 
         /**
-         * Used to track {@link #mSystemGestureExclusionRects}
+         * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects}
          */
         public RenderNode.PositionUpdateListener mPositionUpdateListener;
+        private Runnable mPositionChangedUpdate;
 
         /**
          * Allows the application to implement custom scroll capture support.
@@ -6028,6 +6031,9 @@
                 case R.styleable.View_clipToOutline:
                     setClipToOutline(a.getBoolean(attr, false));
                     break;
+                case R.styleable.View_preferKeepClear:
+                    setPreferKeepClear(a.getBoolean(attr, false));
+                    break;
             }
         }
 
@@ -11665,37 +11671,49 @@
         } else {
             info.mSystemGestureExclusionRects = new ArrayList<>(rects);
         }
-        if (rects.isEmpty()) {
+
+        updatePositionUpdateListener();
+        postUpdate(this::updateSystemGestureExclusionRects);
+    }
+
+    private void updatePositionUpdateListener() {
+        final ListenerInfo info = getListenerInfo();
+        if (getSystemGestureExclusionRects().isEmpty()
+                && collectPreferKeepClearRects().isEmpty()) {
             if (info.mPositionUpdateListener != null) {
                 mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
+                info.mPositionChangedUpdate = null;
             }
         } else {
             if (info.mPositionUpdateListener == null) {
+                info.mPositionChangedUpdate = () -> {
+                    updateSystemGestureExclusionRects();
+                    updateKeepClearRects();
+                };
                 info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() {
                     @Override
                     public void positionChanged(long n, int l, int t, int r, int b) {
-                        postUpdateSystemGestureExclusionRects();
+                        postUpdate(info.mPositionChangedUpdate);
                     }
 
                     @Override
                     public void positionLost(long frameNumber) {
-                        postUpdateSystemGestureExclusionRects();
+                        postUpdate(info.mPositionChangedUpdate);
                     }
                 };
                 mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener);
             }
         }
-        postUpdateSystemGestureExclusionRects();
     }
 
     /**
      * WARNING: this can be called by a hwui worker thread, not just the UI thread!
      */
-    void postUpdateSystemGestureExclusionRects() {
+    private void postUpdate(Runnable r) {
         // Potentially racey from a background thread. It's ok if it's not perfect.
         final Handler h = getHandler();
         if (h != null) {
-            h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects);
+            h.postAtFrontOfQueue(r);
         }
     }
 
@@ -11727,6 +11745,106 @@
     }
 
     /**
+     * Set a preference to keep the bounds of this view clear from floating windows above this
+     * view's window. This informs the system that the view is considered a vital area for the
+     * user and that ideally it should not be covered. Setting this is only appropriate for UI
+     * where the user would likely take action to uncover it.
+     * <p>
+     * The system will try to respect this, but when not possible will ignore it.
+     * <p>
+     * @see #setPreferKeepClearRects
+     * @see #isPreferKeepClear
+     * @attr ref android.R.styleable#View_preferKeepClear
+     */
+    public final void setPreferKeepClear(boolean preferKeepClear) {
+        getListenerInfo().mPreferKeepClear = preferKeepClear;
+        updatePositionUpdateListener();
+        postUpdate(this::updateKeepClearRects);
+    }
+
+    /**
+     * Retrieve the preference for this view to be kept clear. This is set either by
+     * {@link #setPreferKeepClear} or via the attribute android.R.styleable#View_preferKeepClear.
+     * <p>
+     * If this is {@code true}, the system will ignore the Rects set by
+     * {@link #setPreferKeepClearRects} and try to keep the whole view clear.
+     * <p>
+     * @see #setPreferKeepClear
+     * @attr ref android.R.styleable#View_preferKeepClear
+     */
+    public final boolean isPreferKeepClear() {
+        return mListenerInfo != null && mListenerInfo.mPreferKeepClear;
+    }
+
+    /**
+     * Set a preference to keep the provided rects clear from floating windows above this
+     * view's window. This informs the system that these rects are considered vital areas for the
+     * user and that ideally they should not be covered. Setting this is only appropriate for UI
+     * where the user would likely take action to uncover it.
+     * <p>
+     * If the whole view is preferred to be clear ({@link #isPreferKeepClear}), the rects set here
+     * will be ignored.
+     * <p>
+     * The system will try to respect this preference, but when not possible will ignore it.
+     * <p>
+     * @see #setPreferKeepClear
+     * @see #getPreferKeepClearRects
+     */
+    public final void setPreferKeepClearRects(@NonNull List<Rect> rects) {
+        final ListenerInfo info = getListenerInfo();
+        if (info.mKeepClearRects != null) {
+            info.mKeepClearRects.clear();
+            info.mKeepClearRects.addAll(rects);
+        } else {
+            info.mKeepClearRects = new ArrayList<>(rects);
+        }
+        updatePositionUpdateListener();
+        postUpdate(this::updateKeepClearRects);
+    }
+
+    /**
+     * @return the list of rects, set by {@link #setPreferKeepClearRects}.
+     *
+     * @see #setPreferKeepClearRects
+     */
+    @NonNull
+    public final List<Rect> getPreferKeepClearRects() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null && info.mKeepClearRects != null) {
+            return new ArrayList(info.mKeepClearRects);
+        }
+
+        return Collections.emptyList();
+    }
+
+    void updateKeepClearRects() {
+        final AttachInfo ai = mAttachInfo;
+        if (ai != null) {
+            ai.mViewRootImpl.updateKeepClearRectsForView(this);
+        }
+    }
+
+    /**
+     * Retrieve the list of areas within this view's post-layout coordinate space which the
+     * system will try to not cover with other floating elements, like the pip window.
+     */
+    @NonNull
+    List<Rect> collectPreferKeepClearRects() {
+        final ListenerInfo info = mListenerInfo;
+        if (info != null) {
+            final List<Rect> list = new ArrayList();
+            if (info.mPreferKeepClear) {
+                list.add(new Rect(0, 0, getWidth(), getHeight()));
+            } else if (info.mKeepClearRects != null) {
+                list.addAll(info.mKeepClearRects);
+            }
+            return list;
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
      * Compute the view's coordinate within the surface.
      *
      * <p>Computes the coordinates of this view in its surface. The argument
@@ -15120,7 +15238,11 @@
             notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
 
             if (!getSystemGestureExclusionRects().isEmpty()) {
-                postUpdateSystemGestureExclusionRects();
+                postUpdate(this::updateSystemGestureExclusionRects);
+            }
+
+            if (!collectPreferKeepClearRects().isEmpty()) {
+                postUpdate(this::updateKeepClearRects);
             }
         }
     }
@@ -31276,4 +31398,23 @@
         }
         return null;
     }
+
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} instance of the window this view is attached to.
+     *
+     * @return The {@link OnBackInvokedDispatcher} or {@code null} if the view is neither attached
+     *         to a window or a view tree with a decor.
+     */
+    @Nullable
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        ViewParent parent = getParent();
+        if (parent instanceof View) {
+            return ((View) parent).getOnBackInvokedDispatcher();
+        } else if (parent instanceof ViewRootImpl) {
+            // Get the fallback dispatcher on {@link ViewRootImpl} if the view tree doesn't have
+            // a {@link com.android.internal.policy.DecorView}.
+            return ((ViewRootImpl) parent).getOnBackInvokedDispatcher();
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7412931..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;
@@ -192,6 +195,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Scroller;
 import android.window.ClientWindowFrames;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -309,6 +313,12 @@
     private @SurfaceControl.BufferTransform
             int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
     /**
+     * The fallback {@link OnBackInvokedDispatcher} when the window doesn't have a decor view.
+     */
+    private WindowOnBackInvokedDispatcher mFallbackOnBackInvokedDispatcher =
+            new WindowOnBackInvokedDispatcher();
+
+    /**
      * Callback for notifying about global configuration changes.
      */
     public interface ConfigChangedCallback {
@@ -391,6 +401,8 @@
     final DisplayManager mDisplayManager;
     final String mBasePackageName;
 
+    private @Surface.Rotation int mDisplayInstallOrientation;
+
     final int[] mTmpLocation = new int[2];
 
     final TypedValue mTmpValue = new TypedValue();
@@ -462,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)
@@ -742,7 +757,10 @@
         return mImeFocusController;
     }
 
-    private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
+    private final ViewRootRectTracker mGestureExclusionTracker =
+            new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects());
+    private final ViewRootRectTracker mKeepClearRectsTracker =
+            new ViewRootRectTracker(v -> v.collectPreferKeepClearRects());
 
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
 
@@ -871,6 +889,7 @@
         mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
 
         mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
+        mFallbackOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
     }
 
     public static void addFirstDrawHandler(Runnable callback) {
@@ -1017,6 +1036,7 @@
                 mView = view;
 
                 mAttachInfo.mDisplayState = mDisplay.getState();
+                mDisplayInstallOrientation = mDisplay.getInstallOrientation();
                 mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
@@ -1120,6 +1140,9 @@
                     if (pendingInsetsController != null) {
                         pendingInsetsController.replayAndAttach(mInsetsController);
                     }
+                    ((RootViewSurfaceTaker) mView)
+                            .provideWindowOnBackInvokedDispatcher()
+                            .attachToWindow(mWindowSession, mWindow);
                 }
 
                 try {
@@ -2851,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) {
@@ -3232,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.
@@ -3246,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);
@@ -3258,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();
             }
         }
 
@@ -4756,7 +4816,7 @@
      * the root's view hierarchy.
      */
     public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) {
-        mGestureExclusionTracker.setRootSystemGestureExclusionRects(rects);
+        mGestureExclusionTracker.setRootRects(rects);
         mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED);
     }
 
@@ -4766,7 +4826,26 @@
      */
     @NonNull
     public List<Rect> getRootSystemGestureExclusionRects() {
-        return mGestureExclusionTracker.getRootSystemGestureExclusionRects();
+        return mGestureExclusionTracker.getRootRects();
+    }
+
+    /**
+     * Called from View when the position listener is triggered
+     */
+    void updateKeepClearRectsForView(View view) {
+        mKeepClearRectsTracker.updateRectsForView(view);
+        mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED);
+    }
+
+    void keepClearRectsChanged() {
+        final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects();
+        if (rectsForWindowManager != null && mView != null) {
+            try {
+                mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -5262,6 +5341,7 @@
     private static final int MSG_HIDE_INSETS = 35;
     private static final int MSG_REQUEST_SCROLL_CAPTURE = 36;
     private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37;
+    private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 38;
 
 
     final class ViewRootHandler extends Handler {
@@ -5330,6 +5410,8 @@
                     return "MSG_HIDE_INSETS";
                 case MSG_WINDOW_TOUCH_MODE_CHANGED:
                     return "MSG_WINDOW_TOUCH_MODE_CHANGED";
+                case MSG_KEEP_CLEAR_RECTS_CHANGED:
+                    return "MSG_KEEP_CLEAR_RECTS_CHANGED";
             }
             return super.getMessageName(message);
         }
@@ -5553,7 +5635,10 @@
                 } break;
                 case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: {
                     systemGestureExclusionChanged();
-                } break;
+                }   break;
+                case MSG_KEEP_CLEAR_RECTS_CHANGED: {
+                    keepClearRectsChanged();
+                }   break;
                 case MSG_REQUEST_SCROLL_CAPTURE:
                     handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj);
                     break;
@@ -7886,17 +7971,24 @@
             }
         }
 
-        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
@@ -7916,7 +8008,6 @@
                 mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
                 mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue);
             }
-            int transformHint = mSurfaceControl.getTransformHint();
             if (mPreviousTransformHint != transformHint) {
                 mPreviousTransformHint = transformHint;
                 dispatchTransformHintChanged(transformHint);
@@ -10634,7 +10725,7 @@
        }
        mBLASTDrawConsumer = consume;
        return true;
-   }
+    }
 
     boolean wasRelayoutRequested() {
         return mRelayoutRequested;
@@ -10644,4 +10735,27 @@
        mForceNextWindowRelayout = true;
        scheduleTraversals();
     }
+
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} on the decor view if one exists, or the
+     * fallback {@link OnBackInvokedDispatcher} instance.
+     */
+    @Nullable
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        if (mView instanceof RootViewSurfaceTaker) {
+            return ((RootViewSurfaceTaker) mView).provideWindowOnBackInvokedDispatcher();
+        }
+        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/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java
new file mode 100644
index 0000000..fd9cc19
--- /dev/null
+++ b/core/java/android/view/ViewRootRectTracker.java
@@ -0,0 +1,165 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Abstract class to track a collection of rects reported by the views under the same
+ * {@link ViewRootImpl}.
+ */
+class ViewRootRectTracker {
+    private final Function<View, List<Rect>> mRectCollector;
+    private boolean mViewsChanged = false;
+    private boolean mRootRectsChanged = false;
+    private List<Rect> mRootRects = Collections.emptyList();
+    private List<ViewInfo> mViewInfos = new ArrayList<>();
+    private List<Rect> mRects = Collections.emptyList();
+
+    /**
+     * @param rectCollector given a view returns a list of the rects of interest for this
+     *                      ViewRootRectTracker
+     */
+    ViewRootRectTracker(Function<View, List<Rect>> rectCollector) {
+        mRectCollector = rectCollector;
+    }
+
+    public void updateRectsForView(@NonNull View view) {
+        boolean found = false;
+        final Iterator<ViewInfo> i = mViewInfos.iterator();
+        while (i.hasNext()) {
+            final ViewInfo info = i.next();
+            final View v = info.getView();
+            if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
+                mViewsChanged = true;
+                i.remove();
+                continue;
+            }
+            if (v == view) {
+                found = true;
+                info.mDirty = true;
+                break;
+            }
+        }
+        if (!found && view.isAttachedToWindow()) {
+            mViewInfos.add(new ViewInfo(view));
+            mViewsChanged = true;
+        }
+    }
+
+    /**
+     * @return all visible rects from all views in the global (root) coordinate system
+     */
+    @Nullable
+    public List<Rect> computeChangedRects() {
+        boolean changed = mRootRectsChanged;
+        final Iterator<ViewInfo> i = mViewInfos.iterator();
+        final List<Rect> rects = new ArrayList<>(mRootRects);
+        while (i.hasNext()) {
+            final ViewInfo info = i.next();
+            switch (info.update()) {
+                case ViewInfo.CHANGED:
+                    changed = true;
+                    // Deliberate fall-through
+                case ViewInfo.UNCHANGED:
+                    rects.addAll(info.mRects);
+                    break;
+                case ViewInfo.GONE:
+                    mViewsChanged = true;
+                    i.remove();
+                    break;
+            }
+        }
+        if (changed || mViewsChanged) {
+            mViewsChanged = false;
+            mRootRectsChanged = false;
+            if (!mRects.equals(rects)) {
+                mRects = rects;
+                return rects;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets rects defined in the global (root) coordinate system, i.e. not for a specific view.
+     */
+    public void setRootRects(@NonNull List<Rect> rects) {
+        Preconditions.checkNotNull(rects, "rects must not be null");
+        mRootRects = rects;
+        mRootRectsChanged = true;
+    }
+
+    @NonNull
+    public List<Rect> getRootRects() {
+        return mRootRects;
+    }
+
+    @NonNull
+    private List<Rect> getTrackedRectsForView(@NonNull View v) {
+        final List<Rect> rects = mRectCollector.apply(v);
+        return rects == null ? Collections.emptyList() : rects;
+    }
+
+    private class ViewInfo {
+        public static final int CHANGED = 0;
+        public static final int UNCHANGED = 1;
+        public static final int GONE = 2;
+
+        private final WeakReference<View> mView;
+        boolean mDirty = true;
+        List<Rect> mRects = Collections.emptyList();
+
+        ViewInfo(View view) {
+            mView = new WeakReference<>(view);
+        }
+
+        public View getView() {
+            return mView.get();
+        }
+
+        public int update() {
+            final View view = getView();
+            if (view == null || !view.isAttachedToWindow()
+                    || !view.isAggregatedVisible()) return GONE;
+            final List<Rect> localRects = getTrackedRectsForView(view);
+            final List<Rect> newRects = new ArrayList<>(localRects.size());
+            for (Rect src : localRects) {
+                Rect mappedRect = new Rect(src);
+                ViewParent p = view.getParent();
+                if (p != null && p.getChildVisibleRect(view, mappedRect, null)) {
+                    newRects.add(mappedRect);
+                }
+            }
+
+            if (mRects.equals(localRects)) return UNCHANGED;
+            mRects = newRects;
+            return CHANGED;
+        }
+    }
+}
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/WindowManager.java b/core/java/android/view/WindowManager.java
index 5be3a57..ca7f900 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -118,6 +118,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.window.TaskFpsCallback;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -4858,4 +4859,31 @@
     default boolean isTaskSnapshotSupported() {
         return false;
     }
+
+    /**
+     * Registers the frame rate per second count callback for one given task ID.
+     * Each callback can only register for receiving FPS callback for one task id until unregister
+     * is called. If there's no task associated with the given task id,
+     * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is
+     * registered, the registered callback will not be unregistered until
+     * {@link #unregisterTaskFpsCallback(TaskFpsCallback))} is called
+     * @param taskId task id of the task.
+     * @param callback callback to be registered.
+     *
+     * @hide
+     */
+    @SystemApi
+    default void registerTaskFpsCallback(@IntRange(from = 0) int taskId,
+            @NonNull TaskFpsCallback callback) {}
+
+    /**
+     * Unregisters the frame rate per second count callback which was registered with
+     * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}.
+     *
+     * @param callback callback to be unregistered.
+     *
+     * @hide
+     */
+    @SystemApi
+    default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {}
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index dd80416..c16703ef 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -24,6 +24,7 @@
 import static android.window.WindowProviderService.isWindowProviderService;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -37,6 +38,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.StrictMode;
+import android.window.TaskFpsCallback;
 import android.window.WindowContext;
 import android.window.WindowProvider;
 
@@ -419,4 +421,22 @@
         }
         return false;
     }
+
+    @Override
+    public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, TaskFpsCallback callback) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().registerTaskFpsCallback(
+                    taskId, callback.getListener());
+        } catch (RemoteException e) {
+        }
+    }
+
+    @Override
+    public void unregisterTaskFpsCallback(TaskFpsCallback callback) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().unregisterTaskFpsCallback(
+                    callback.getListener());
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 5176f9b..5ec9654 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -19,15 +19,16 @@
 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.Binder;
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.window.ClientWindowFrames;
+import android.window.IOnBackInvokedCallback;
 
 import java.util.HashMap;
 import java.util.Objects;
@@ -75,6 +76,7 @@
     private final Configuration mConfiguration;
     private final IWindowSession mRealWm;
     private final IBinder mHostInputToken;
+    private final IBinder mFocusGrantToken = new Binder();
 
     private int mForceHeight = -1;
     private int mForceWidth = -1;
@@ -91,6 +93,10 @@
         mConfiguration.setTo(configuration);
     }
 
+    IBinder getFocusGrantToken() {
+        return mFocusGrantToken;
+    }
+
     /**
      * Utility API.
      */
@@ -153,10 +159,10 @@
                     mRealWm.grantInputChannel(displayId,
                         new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
                         window, mHostInputToken, attrs.flags, attrs.privateFlags, attrs.type,
-                        outInputChannel);
+                        mFocusGrantToken, outInputChannel);
                 } else {
                     mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
-                        attrs.privateFlags, attrs.type, outInputChannel);
+                        attrs.privateFlags, attrs.type, mFocusGrantToken, outInputChannel);
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to grant input to surface: ", e);
@@ -263,10 +269,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());
@@ -285,7 +291,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 {
@@ -334,6 +339,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) {
@@ -459,8 +469,13 @@
     }
 
     @Override
+    public void reportKeepClearAreasChanged(android.view.IWindow window,
+            java.util.List<android.graphics.Rect> exclusionRects) {
+    }
+
+    @Override
     public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
-            IBinder hostInputToken, int flags, int privateFlags, int type,
+            IBinder hostInputToken, int flags, int privateFlags, int type, IBinder focusGrantToken,
             InputChannel outInputChannel) {
     }
 
@@ -474,17 +489,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) {
@@ -496,6 +500,10 @@
     }
 
     @Override
+    public void setOnBackInvokedCallback(IWindow iWindow,
+            IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { }
+
+    @Override
     public boolean dropForAccessibility(IWindow window, int x, int y) {
         return false;
     }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 6ad2d9a..a427ab8 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -1315,7 +1315,7 @@
         record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mParcelableData = parcel.readParcelable(null);
-        parcel.readList(record.mText, null);
+        parcel.readList(record.mText, null, java.lang.CharSequence.class);
         record.mSourceWindowId = parcel.readInt();
         record.mSourceNodeId = parcel.readLong();
         record.mSourceDisplayId = parcel.readInt();
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 67e6d3f..540f5dc 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -917,7 +917,7 @@
                 final int count = source.readInt();
                 for (int i = 0; i < count; i++) {
                     List<AccessibilityWindowInfo> windows = new ArrayList<>();
-                    source.readParcelableList(windows, loader);
+                    source.readParcelableList(windows, loader, android.view.accessibility.AccessibilityWindowInfo.class);
                     array.put(source.readInt(), windows);
                 }
                 return array;
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 05c74f2..4f9781b 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -254,7 +254,13 @@
      * Returns true if system wide call captioning is enabled for this device.
      */
     public boolean isCallCaptioningEnabled() {
-        return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled);
+        try {
+            return mResources.getBoolean(
+                R.bool.config_systemCaptionsServiceCallsEnabled);
+        } catch (Resources.NotFoundException e) {
+            // The resource may not be defined, return false in that case
+            return false;
+        }
     }
 
     private void notifyEnabledChanged() {
diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java
index d8459aa..3fa7734 100644
--- a/core/java/android/view/autofill/ParcelableMap.java
+++ b/core/java/android/view/autofill/ParcelableMap.java
@@ -56,8 +56,8 @@
                     ParcelableMap map = new ParcelableMap(size);
 
                     for (int i = 0; i < size; i++) {
-                        AutofillId key = source.readParcelable(null);
-                        AutofillValue value = source.readParcelable(null);
+                        AutofillId key = source.readParcelable(null, android.view.autofill.AutofillId.class);
+                        AutofillValue value = source.readParcelable(null, android.view.autofill.AutofillValue.class);
 
                         map.put(key, value);
                     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java
index 027c8d2..685ea1a 100644
--- a/core/java/android/view/contentcapture/ContentCaptureCondition.java
+++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java
@@ -133,7 +133,7 @@
 
                 @Override
                 public ContentCaptureCondition createFromParcel(@NonNull Parcel parcel) {
-                    return new ContentCaptureCondition(parcel.readParcelable(null),
+                    return new ContentCaptureCondition(parcel.readParcelable(null, android.content.LocusId.class),
                             parcel.readInt());
                 }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 3bc9a96..59b5286 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -419,7 +419,7 @@
             final ContentCaptureContext clientContext;
             if (hasClientContext) {
                 // Must reconstruct the client context using the Builder API
-                final LocusId id = parcel.readParcelable(null);
+                final LocusId id = parcel.readParcelable(null, android.content.LocusId.class);
                 final Bundle extras = parcel.readBundle();
                 final Builder builder = new Builder(id);
                 if (extras != null) builder.setExtras(extras);
@@ -427,7 +427,7 @@
             } else {
                 clientContext = null;
             }
-            final ComponentName componentName = parcel.readParcelable(null);
+            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
             if (componentName == null) {
                 // Client-state only
                 return clientContext;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 0f4bc19..ba4176f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -620,7 +620,7 @@
             final int type = parcel.readInt();
             final long eventTime  = parcel.readLong();
             final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
-            final AutofillId id = parcel.readParcelable(null);
+            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
             if (id != null) {
                 event.setAutofillId(id);
             }
@@ -637,13 +637,13 @@
                 event.setParentSessionId(parcel.readInt());
             }
             if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
-                event.setClientContext(parcel.readParcelable(null));
+                event.setClientContext(parcel.readParcelable(null, android.view.contentcapture.ContentCaptureContext.class));
             }
             if (type == TYPE_VIEW_INSETS_CHANGED) {
-                event.setInsets(parcel.readParcelable(null));
+                event.setInsets(parcel.readParcelable(null, android.graphics.Insets.class));
             }
             if (type == TYPE_WINDOW_BOUNDS_CHANGED) {
-                event.setBounds(parcel.readParcelable(null));
+                event.setBounds(parcel.readParcelable(null, android.graphics.Rect.class));
             }
             if (type == TYPE_VIEW_TEXT_CHANGED) {
                 event.setComposingIndex(parcel.readInt(), parcel.readInt());
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 1b4a00f..1762a58 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -124,10 +124,10 @@
         mFlags = nodeFlags;
 
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
-            mAutofillId = parcel.readParcelable(null);
+            mAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
-            mParentAutofillId = parcel.readParcelable(null);
+            mParentAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
         }
         if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
             mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
@@ -169,7 +169,7 @@
             mExtras = parcel.readBundle();
         }
         if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
-            mLocaleList = parcel.readParcelable(null);
+            mLocaleList = parcel.readParcelable(null, android.os.LocaleList.class);
         }
         if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) {
             mReceiveContentMimeTypes = parcel.readStringArray();
@@ -196,7 +196,7 @@
             mAutofillHints = parcel.readStringArray();
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-            mAutofillValue = parcel.readParcelable(null);
+            mAutofillValue = parcel.readParcelable(null, android.view.autofill.AutofillValue.class);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
             mAutofillOptions = parcel.readCharSequenceArray();
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index e3d3bfd..8600f55 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -147,7 +147,7 @@
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
-        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
+        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
         mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR);
         mMatrixValues = source.createFloatArray();
     }
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 4cbd477..09a1448 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -1067,7 +1067,7 @@
                     res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.packageName = source.readString();
-                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader());
+                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader(), android.view.autofill.AutofillId.class);
                     res.fieldId = source.readInt();
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index e1e1755..70279cc 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -490,7 +490,7 @@
         boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index b393c67..532fc85 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -170,7 +170,7 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         List<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader());
+        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader(), android.view.inputmethod.InlineSuggestion.class);
 
         this.mInlineSuggestions = inlineSuggestions;
         com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6fc246e..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);
@@ -2139,7 +2149,7 @@
             view.onInputConnectionOpenedInternal(ic, tba, icHandler);
             final ViewRootImpl viewRoot = view.getViewRootImpl();
             if (viewRoot != null) {
-                viewRoot.getHandwritingInitiator().onInputConnectionCreated(view, tba);
+                viewRoot.getHandwritingInitiator().onInputConnectionCreated(view);
             }
         }
 
@@ -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/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index bf0409d..a4a5a1e 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -141,7 +141,7 @@
 
     private ConversationAction(Parcel in) {
         mType = in.readString();
-        mAction = in.readParcelable(null);
+        mAction = in.readParcelable(null, android.app.RemoteAction.class);
         mTextReply = in.readCharSequence();
         mScore = in.readFloat();
         mExtras = in.readBundle();
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 6ad5cb9..7a6a3cd 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -149,7 +149,7 @@
         }
 
         private Message(Parcel in) {
-            mAuthor = in.readParcelable(null);
+            mAuthor = in.readParcelable(null, android.app.Person.class);
             mReferenceTime =
                     in.readInt() == 0
                             ? null
@@ -331,13 +331,13 @@
 
         private static Request readFromParcel(Parcel in) {
             List<Message> conversation = new ArrayList<>();
-            in.readParcelableList(conversation, null);
-            TextClassifier.EntityConfig typeConfig = in.readParcelable(null);
+            in.readParcelableList(conversation, null, android.view.textclassifier.ConversationActions.Message.class);
+            TextClassifier.EntityConfig typeConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
             int maxSuggestions = in.readInt();
             List<String> hints = new ArrayList<>();
             in.readStringList(hints);
             Bundle extras = in.readBundle();
-            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             Request request = new Request(
                     conversation,
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 858825b..b347010 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -172,7 +172,7 @@
         mEnd = in.readInt();
         mSmartStart = in.readInt();
         mSmartEnd = in.readInt();
-        mSystemTcMetadata = in.readParcelable(null);
+        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
     }
 
     @Override
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 7db35d4..8b04d35 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -713,12 +713,12 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, startIndex, endIndex,
                     defaultLocales, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 5d5683f..3a50809 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -159,7 +159,7 @@
         mPackageName = in.readString();
         mWidgetType = in.readString();
         mWidgetVersion = in.readString();
-        mSystemTcMetadata = in.readParcelable(null);
+        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index 90667cf..195565c 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -189,7 +189,7 @@
         mEventCategory = in.readInt();
         mEventType = in.readInt();
         mEntityTypes = in.readStringArray();
-        mEventContext = in.readParcelable(null);
+        mEventContext = in.readParcelable(null, android.view.textclassifier.TextClassificationContext.class);
         mResultId = in.readString();
         mEventIndex = in.readInt();
         int scoresLength = in.readInt();
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index 604979b..67167c6 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -295,7 +295,7 @@
         private static Request readFromParcel(Parcel in) {
             final CharSequence text = in.readCharSequence();
             final Bundle extra = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, extra);
             request.setSystemTextClassifierMetadata(systemTcMetadata);
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index dea3a90..445e9ec 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -558,13 +558,13 @@
 
         private static Request readFromParcel(Parcel in) {
             final String text = in.readString();
-            final LocaleList defaultLocales = in.readParcelable(null);
-            final EntityConfig entityConfig = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
+            final EntityConfig entityConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
             final Bundle extras = in.readBundle();
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, defaultLocales, entityConfig,
                     /* legacyFallback= */ true, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index c1913f6..dda0fcdd 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -489,9 +489,9 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
             final boolean includeTextClassification = in.readBoolean();
 
             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
@@ -548,6 +548,6 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
         mExtras = in.readBundle();
-        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader());
+        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class);
     }
 }
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index 0d41851..027edc2 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -255,9 +255,9 @@
 
         int flags = in.readInt();
         List<TranslationRequestValue> translationRequestValues = new ArrayList<>();
-        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader());
+        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader(), android.view.translation.TranslationRequestValue.class);
         List<ViewTranslationRequest> viewTranslationRequests = new ArrayList<>();
-        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader());
+        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader(), android.view.translation.ViewTranslationRequest.class);
 
         this.mFlags = flags;
 
diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java
index efc3d8b..76dda5f 100644
--- a/core/java/android/view/translation/TranslationSpec.java
+++ b/core/java/android/view/translation/TranslationSpec.java
@@ -64,7 +64,7 @@
     }
 
     static ULocale unparcelLocale(Parcel in) {
-        return (ULocale) in.readSerializable();
+        return (ULocale) in.readSerializable(android.icu.util.ULocale.class.getClassLoader(), android.icu.util.ULocale.class);
     }
 
     /**
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/Editor.java b/core/java/android/widget/Editor.java
index ac28c31..dd70d69 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;
 
 import android.R;
 import android.animation.ValueAnimator;
@@ -84,6 +85,7 @@
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.ActionMode;
@@ -112,6 +114,7 @@
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.LinearInterpolator;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
@@ -449,11 +452,14 @@
     private int mLineChangeSlopMax;
     private int mLineChangeSlopMin;
 
+    private final AccessibilitySmartActions mA11ySmartActions;
+
     Editor(TextView textView) {
         mTextView = textView;
         // Synchronize the filter list, which places the undo input filter at the end.
         mTextView.setFilters(mTextView.getFilters());
         mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
+        mA11ySmartActions = new AccessibilitySmartActions(mTextView);
         mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
 
@@ -4381,6 +4387,7 @@
             item.setShowAsAction(showAsAction);
             mAssistClickHandlers.put(item,
                     TextClassification.createIntentOnClickListener(action.getActionIntent()));
+            mA11ySmartActions.addAction(action);
             return item;
         }
 
@@ -4394,6 +4401,7 @@
                 }
                 i++;
             }
+            mA11ySmartActions.reset();
         }
 
         private boolean hasLegacyAssistItem(TextClassification classification) {
@@ -7656,7 +7664,7 @@
         private final PackageManager mPackageManager;
         private final String mPackageName;
         private final SparseArray<Intent> mAccessibilityIntents = new SparseArray<>();
-        private final SparseArray<AccessibilityNodeInfo.AccessibilityAction> mAccessibilityActions =
+        private final SparseArray<AccessibilityAction> mAccessibilityActions =
                 new SparseArray<>();
         private final List<ResolveInfo> mSupportedActivities = new ArrayList<>();
 
@@ -7706,8 +7714,7 @@
                 int actionId = TextView.ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID + i++;
                 mAccessibilityActions.put(
                         actionId,
-                        new AccessibilityNodeInfo.AccessibilityAction(
-                                actionId, getLabel(resolveInfo)));
+                        new AccessibilityAction(actionId, getLabel(resolveInfo)));
                 mAccessibilityIntents.put(
                         actionId, createProcessTextIntentForResolveInfo(resolveInfo));
             }
@@ -7786,6 +7793,65 @@
         }
     }
 
+    /**
+     * Accessibility helper for "smart" (i.e. textAssist) actions.
+     * Helps ensure that "smart" actions are shown in the accessibility menu.
+     * NOTE that these actions are only available when an action mode is live.
+     *
+     * @hide
+     */
+    private static final class AccessibilitySmartActions {
+
+        private final TextView mTextView;
+        private final SparseArray<Pair<AccessibilityAction, RemoteAction>> mActions =
+                new SparseArray<>();
+
+        private AccessibilitySmartActions(TextView textView) {
+            mTextView = Objects.requireNonNull(textView);
+        }
+
+        private void addAction(RemoteAction action) {
+            final int actionId = ACCESSIBILITY_ACTION_SMART_START_ID + mActions.size();
+            mActions.put(actionId,
+                    new Pair(new AccessibilityAction(actionId, action.getTitle()), action));
+        }
+
+        private void reset() {
+            mActions.clear();
+        }
+
+        void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) {
+            for (int i = 0; i < mActions.size(); i++) {
+                nodeInfo.addAction(mActions.valueAt(i).first);
+            }
+        }
+
+        boolean performAccessibilityAction(int actionId) {
+            final Pair<AccessibilityAction, RemoteAction> pair = mActions.get(actionId);
+            if (pair != null) {
+                TextClassification.createIntentOnClickListener(pair.second.getActionIntent())
+                        .onClick(mTextView);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Initializes the nodeInfo with smart actions.
+     */
+    void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) {
+        mA11ySmartActions.onInitializeAccessibilityNodeInfo(nodeInfo);
+    }
+
+    /**
+     * Handles the accessibility action if it is an active smart action.
+     * Return false if this method does not hanle the action.
+     */
+    boolean performSmartActionsAccessibilityAction(int actionId) {
+        return mA11ySmartActions.performAccessibilityAction(actionId);
+    }
+
     static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
         if (msgFormat == null) {
             Log.d(TAG, location);
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 51869d4..e243aae 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1309,7 +1309,7 @@
         private SavedState(Parcel in) {
             super(in);
             expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
-            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
+            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader(), android.widget.ExpandableListConnector.GroupMetadata.class);
         }
 
         @Override
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e60f9a6..c6f64f4 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1489,7 +1489,7 @@
 
         SetRippleDrawableColor(Parcel parcel) {
             viewId = parcel.readInt();
-            mColorStateList = parcel.readParcelable(null);
+            mColorStateList = parcel.readParcelable(null, android.content.res.ColorStateList.class);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
@@ -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 0fe06be..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
@@ -442,6 +443,9 @@
     // Accessibility action start id for "process text" actions.
     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
 
+    /** Accessibility action start id for "smart" actions. @hide */
+    static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000;
+
     /**
      * @hide
      */
@@ -458,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
@@ -1450,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;
@@ -3982,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() {
@@ -4012,6 +4032,10 @@
                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
+                    + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
+                    + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
+                    + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
+                    + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
                     + "}";
         }
     }
@@ -4059,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);
     }
 
     /**
@@ -4174,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:
             }
         }
@@ -4239,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.
@@ -4797,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;
         }
@@ -4855,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();
@@ -12223,6 +12315,7 @@
             }
             if (canProcessText()) {  // also implies mEditor is not null.
                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
+                mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info);
             }
         }
 
@@ -12426,9 +12519,11 @@
      */
     @Override
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        if (mEditor != null
-                && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
-            return true;
+        if (mEditor != null) {
+            if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)
+                    || mEditor.performSmartActionsAccessibilityAction(action)) {
+                return true;
+            }
         }
         switch (action) {
             case AccessibilityNodeInfo.ACTION_CLICK: {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/core/java/android/window/BackNavigationInfo.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to core/java/android/window/BackNavigationInfo.aidl
index 2b3e961..1529902 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/core/java/android/window/BackNavigationInfo.aidl
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.window;
 
-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 BackNavigationInfo;
\ No newline at end of file
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
new file mode 100644
index 0000000..571714c
--- /dev/null
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -0,0 +1,242 @@
+/*
+ * 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.window;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteCallback;
+import android.view.SurfaceControl;
+
+/**
+ * Information to be sent to SysUI about a back event.
+ *
+ * @hide
+ */
+public final class BackNavigationInfo implements Parcelable {
+
+    /**
+     * The target of the back navigation is undefined.
+     */
+    public static final int TYPE_UNDEFINED = -1;
+
+    /**
+     * Navigating back will close the currently visible dialog
+     */
+    public static final int TYPE_DIALOG_CLOSE = 0;
+
+    /**
+     * Navigating back will bring the user back to the home screen
+     */
+    public static final int TYPE_RETURN_TO_HOME = 1;
+
+    /**
+     * Navigating back will bring the user to the previous activity in the same Task
+     */
+    public static final int TYPE_CROSS_ACTIVITY = 2;
+
+    /**
+     * Navigating back will bring the user to the previous activity in the previous Task
+     */
+    public static final int TYPE_CROSS_TASK = 3;
+
+    /**
+     * Defines the type of back destinations a back even can lead to. This is used to define the
+     * type of animation that need to be run on SystemUI.
+     */
+    @IntDef(prefix = "TYPE_", value = {
+            TYPE_UNDEFINED,
+            TYPE_DIALOG_CLOSE,
+            TYPE_RETURN_TO_HOME,
+            TYPE_CROSS_ACTIVITY,
+            TYPE_CROSS_TASK})
+    @interface BackTargetType {
+    }
+
+    private final int mType;
+    @Nullable
+    private final SurfaceControl mDepartingWindowContainer;
+    @Nullable
+    private final SurfaceControl mScreenshotSurface;
+    @Nullable
+    private final HardwareBuffer mScreenshotBuffer;
+    @Nullable
+    private final RemoteCallback mRemoteCallback;
+    @Nullable
+    private final WindowConfiguration mTaskWindowConfiguration;
+
+    /**
+     * Create a new {@link BackNavigationInfo} instance.
+     *
+     * @param type  The {@link BackTargetType} of the destination (what will be displayed after
+     *              the back action)
+     * @param topWindowLeash      The leash to animate away the current topWindow. The consumer
+     *                            of the leash is responsible for removing it.
+     * @param screenshotSurface The screenshot of the previous activity to be displayed.
+     * @param screenshotBuffer      A buffer containing a screenshot used to display the activity.
+     *                            See {@link  #getScreenshotHardwareBuffer()} for information
+     *                            about nullity.
+     * @param taskWindowConfiguration The window configuration of the Task being animated
+     *                            beneath.
+     * @param onBackNavigationDone   The callback to be called once the client is done with the back
+     *                           preview.
+     */
+    public BackNavigationInfo(@BackTargetType int type,
+            @Nullable SurfaceControl topWindowLeash,
+            @Nullable SurfaceControl screenshotSurface,
+            @Nullable HardwareBuffer screenshotBuffer,
+            @Nullable WindowConfiguration taskWindowConfiguration,
+            @NonNull RemoteCallback onBackNavigationDone) {
+        mType = type;
+        mDepartingWindowContainer = topWindowLeash;
+        mScreenshotSurface = screenshotSurface;
+        mScreenshotBuffer = screenshotBuffer;
+        mTaskWindowConfiguration = taskWindowConfiguration;
+        mRemoteCallback = onBackNavigationDone;
+    }
+
+    private BackNavigationInfo(@NonNull Parcel in) {
+        mType = in.readInt();
+        mDepartingWindowContainer = in.readTypedObject(SurfaceControl.CREATOR);
+        mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR);
+        mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR);
+        mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
+        mRemoteCallback = requireNonNull(in.readTypedObject(RemoteCallback.CREATOR));
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeTypedObject(mDepartingWindowContainer, flags);
+        dest.writeTypedObject(mScreenshotSurface, flags);
+        dest.writeTypedObject(mScreenshotBuffer, flags);
+        dest.writeTypedObject(mTaskWindowConfiguration, flags);
+        dest.writeTypedObject(mRemoteCallback, flags);
+    }
+
+    /**
+     * Returns the type of back navigation that is about to happen.
+     * @see BackTargetType
+     */
+    public @BackTargetType int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns a leash to the top window container that needs to be animated. This can be null if
+     * the back animation is controlled by the application.
+     */
+    @Nullable
+    public SurfaceControl getDepartingWindowContainer() {
+        return mDepartingWindowContainer;
+    }
+
+    /**
+     *  Returns the {@link SurfaceControl} that should be used to display a screenshot of the
+     *  previous activity.
+     */
+    @Nullable
+    public SurfaceControl getScreenshotSurface() {
+        return mScreenshotSurface;
+    }
+
+    /**
+     * Returns the {@link HardwareBuffer} containing the screenshot the activity about to be
+     * shown. This can be null if one of the following conditions is met:
+     * <ul>
+     *     <li>The screenshot is not available
+     *     <li> The previous activity is the home screen ( {@link  #TYPE_RETURN_TO_HOME}
+     *     <li> The current window is a dialog ({@link  #TYPE_DIALOG_CLOSE}
+     *     <li> The back animation is controlled by the application
+     * </ul>
+     */
+    @Nullable
+    public HardwareBuffer getScreenshotHardwareBuffer() {
+        return mScreenshotBuffer;
+    }
+
+    /**
+     * Returns the {@link WindowConfiguration} of the current task. This is null when the top
+     * application is controlling the back animation.
+     */
+    @Nullable
+    public WindowConfiguration getTaskWindowConfiguration() {
+        return mTaskWindowConfiguration;
+    }
+
+    /**
+     * Callback to be called when the back preview is finished in order to notify the server that
+     * it can clean up the resources created for the animation.
+     */
+    public void onBackNavigationFinished() {
+        mRemoteCallback.sendResult(null);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<BackNavigationInfo> CREATOR = new Creator<BackNavigationInfo>() {
+        @Override
+        public BackNavigationInfo createFromParcel(Parcel in) {
+            return new BackNavigationInfo(in);
+        }
+
+        @Override
+        public BackNavigationInfo[] newArray(int size) {
+            return new BackNavigationInfo[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return "BackNavigationInfo{"
+                + "mType=" + typeToString(mType) + " (" + mType + ")"
+                + ", mDepartingWindowContainer=" + mDepartingWindowContainer
+                + ", mScreenshotSurface=" + mScreenshotSurface
+                + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration
+                + ", mScreenshotBuffer=" + mScreenshotBuffer
+                + ", mRemoteCallback=" + mRemoteCallback
+                + '}';
+    }
+
+    /**
+     * Translates the {@link BackNavigationInfo} integer type to its String representation
+     */
+    public static String typeToString(@BackTargetType int type) {
+        switch (type) {
+            case  TYPE_UNDEFINED:
+                return "TYPE_UNDEFINED";
+            case TYPE_DIALOG_CLOSE:
+                return "TYPE_DIALOG_CLOSE";
+            case TYPE_RETURN_TO_HOME:
+                return "TYPE_RETURN_TO_HOME";
+            case TYPE_CROSS_ACTIVITY:
+                return "TYPE_CROSS_ACTIVITY";
+            case TYPE_CROSS_TASK:
+                return "TYPE_CROSS_TASK";
+        }
+        return String.valueOf(type);
+    }
+}
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
new file mode 100644
index 0000000..a42863c
--- /dev/null
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.window;
+
+/**
+ * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
+ * and called from back handling process when back is invoked.
+ *
+ * @hide
+ */
+oneway interface IOnBackInvokedCallback {
+   /**
+    * Called when a back gesture has been started, or back button has been pressed down.
+    * Wraps {@link OnBackInvokedCallback#onBackStarted()}.
+    */
+    void onBackStarted();
+
+    /**
+     * Called on back gesture progress.
+     * Wraps {@link OnBackInvokedCallback#onBackProgressed()}.
+     *
+     * @param touchX Absolute X location of the touch point.
+     * @param touchY Absolute Y location of the touch point.
+     * @param progress Value between 0 and 1 on how far along the back gesture is.
+     */
+    void onBackProgressed(int touchX, int touchY, float progress);
+
+    /**
+     * Called when a back gesture or back button press has been cancelled.
+     * Wraps {@link OnBackInvokedCallback#onBackCancelled()}.
+     */
+    void onBackCancelled();
+
+    /**
+     * Called when a back gesture has been completed and committed, or back button pressed
+     * has been released and committed.
+     * Wraps {@link OnBackInvokedCallback#onBackInvoked()}.
+     */
+    void onBackInvoked();
+}
diff --git a/core/java/android/window/IOnFpsCallbackListener.aidl b/core/java/android/window/IOnFpsCallbackListener.aidl
new file mode 100644
index 0000000..3091df3
--- /dev/null
+++ b/core/java/android/window/IOnFpsCallbackListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.window;
+
+/**
+ * @hide
+ */
+oneway interface IOnFpsCallbackListener {
+
+    /**
+     * Reports the fps from the registered task
+     * @param fps The frame rate per second of the task that has the registered task id
+     *            and its children.
+     */
+    void onFpsReported(in float fps);
+}
diff --git a/core/java/android/window/TaskFpsCallback.java b/core/java/android/window/TaskFpsCallback.java
new file mode 100644
index 0000000..a8e01b6
--- /dev/null
+++ b/core/java/android/window/TaskFpsCallback.java
@@ -0,0 +1,86 @@
+/*
+ * 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.window;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Callback for sampling the frames per second for a task and its children.
+ * This should only be used by a system component that needs to listen to a task's
+ * tree's FPS when it is not actively submitting transactions for that corresponding SurfaceControl.
+ * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used.
+ *
+ * Each callback can only register for receiving FPS report for one task id until
+ * {@link WindowManager#unregister()} is called.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TaskFpsCallback {
+
+    /**
+     * Listener interface to receive frame per second of a task.
+     */
+    public interface OnFpsCallbackListener {
+        /**
+         * Reports the fps from the registered task
+         * @param fps The frame per second of the task that has the registered task id
+         *            and its children.
+         */
+        void onFpsReported(float fps);
+    }
+
+    private final IOnFpsCallbackListener mListener;
+
+    public TaskFpsCallback(@NonNull Executor executor, @NonNull OnFpsCallbackListener listener) {
+        mListener = new IOnFpsCallbackListener.Stub() {
+            @Override
+            public void onFpsReported(float fps) {
+                executor.execute(() -> {
+                    listener.onFpsReported(fps);
+                });
+            }
+        };
+    }
+
+    /**
+     * @hide
+     */
+    public IOnFpsCallbackListener getListener() {
+        return mListener;
+    }
+
+    /**
+     * Dispatch the collected sample.
+     *
+     * Called from native code on a binder thread.
+     */
+    @BinderThread
+    private static void dispatchOnFpsReported(
+            @NonNull IOnFpsCallbackListener listener, float fps) {
+        try {
+            listener.onFpsReported(fps);
+        } catch (RemoteException e) {
+            /* ignore */
+        }
+    }
+}
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/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
new file mode 100644
index 0000000..2978604
--- /dev/null
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -0,0 +1,201 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.IWindowSession;
+import android.view.OnBackInvokedCallback;
+import android.view.OnBackInvokedDispatcher;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Provides window based implementation of {@link OnBackInvokedDispatcher}.
+ *
+ * Callbacks with higher priorities receive back dispatching first.
+ * Within the same priority, callbacks receive back dispatching in the reverse order
+ * in which they are added.
+ *
+ * When the top priority callback is updated, the new callback is propagated to the Window Manager
+ * if the window the instance is associated with has been attached. It is allowed to register /
+ * unregister {@link OnBackInvokedCallback}s before the window is attached, although callbacks
+ * will not receive dispatches until window attachment.
+ *
+ * @hide
+ */
+public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
+    private IWindowSession mWindowSession;
+    private IWindow mWindow;
+    private static final String TAG = "WindowOnBackDispatcher";
+    private static final boolean DEBUG = false;
+
+    /** The currently most prioritized callback. */
+    @Nullable
+    private OnBackInvokedCallbackWrapper mTopCallback;
+
+    /** Convenience hashmap to quickly decide if a callback has been added. */
+    private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
+    /** Holds all callbacks by priorities. */
+    private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
+            mOnBackInvokedCallbacks = new TreeMap<>();
+
+    /**
+     * Sends the pending top callback (if one exists) to WM when the view root
+     * is attached a window.
+     */
+    public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) {
+        mWindowSession = windowSession;
+        mWindow = window;
+        if (mTopCallback != null) {
+            setTopOnBackInvokedCallback(mTopCallback);
+        }
+    }
+
+    /** Detaches the dispatcher instance from its window. */
+    public void detachFromWindow() {
+        mWindow = null;
+        mWindowSession = null;
+    }
+
+    // TODO: Take an Executor for the callback to run on.
+    @Override
+    public void registerOnBackInvokedCallback(
+            @NonNull OnBackInvokedCallback callback, @Priority int priority) {
+        if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+            mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+        }
+        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+
+        // If callback has already been added, remove it and re-add it.
+        if (mAllCallbacks.containsKey(callback)) {
+            if (DEBUG) {
+                Log.i(TAG, "Callback already added. Removing and re-adding it.");
+            }
+            Integer prevPriority = mAllCallbacks.get(callback);
+            mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
+        }
+
+        callbacks.add(callback);
+        mAllCallbacks.put(callback, priority);
+        if (mTopCallback == null || (mTopCallback.getCallback() != callback
+                && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) {
+            setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority));
+        }
+    }
+
+    @Override
+    public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
+        if (!mAllCallbacks.containsKey(callback)) {
+            if (DEBUG) {
+                Log.i(TAG, "Callback not found. returning...");
+            }
+            return;
+        }
+        Integer priority = mAllCallbacks.get(callback);
+        mOnBackInvokedCallbacks.get(priority).remove(callback);
+        mAllCallbacks.remove(callback);
+        if (mTopCallback != null && mTopCallback.getCallback() == callback) {
+            findAndSetTopOnBackInvokedCallback();
+        }
+    }
+
+    /** Clears all registered callbacks on the instance. */
+    public void clear() {
+        mAllCallbacks.clear();
+        mTopCallback = null;
+        mOnBackInvokedCallbacks.clear();
+    }
+
+    /**
+     * Iterates through all callbacks to find the most prioritized one and pushes it to
+     * window manager.
+     */
+    private void findAndSetTopOnBackInvokedCallback() {
+        if (mAllCallbacks.isEmpty()) {
+            setTopOnBackInvokedCallback(null);
+            return;
+        }
+
+        for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+            if (!callbacks.isEmpty()) {
+                OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper(
+                        callbacks.get(callbacks.size() - 1), priority);
+                setTopOnBackInvokedCallback(callback);
+                return;
+            }
+        }
+        setTopOnBackInvokedCallback(null);
+    }
+
+    // Pushes the top priority callback to window manager.
+    private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) {
+        mTopCallback = callback;
+        if (mWindowSession == null || mWindow == null) {
+            return;
+        }
+        try {
+            mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);
+        }
+    }
+
+    private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
+        private final OnBackInvokedCallback mCallback;
+        private final @Priority int mPriority;
+
+        OnBackInvokedCallbackWrapper(
+                @NonNull OnBackInvokedCallback callback, @Priority int priority) {
+            mCallback = callback;
+            mPriority = priority;
+        }
+
+        @NonNull
+        public OnBackInvokedCallback getCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public void onBackStarted() throws RemoteException {
+            Handler.getMain().post(() -> mCallback.onBackStarted());
+        }
+
+        @Override
+        public void onBackProgressed(int touchX, int touchY, float progress)
+                throws RemoteException {
+            Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress));
+        }
+
+        @Override
+        public void onBackCancelled() throws RemoteException {
+            Handler.getMain().post(() -> mCallback.onBackCancelled());
+        }
+
+        @Override
+        public void onBackInvoked() throws RemoteException {
+            Handler.getMain().post(() -> mCallback.onBackInvoked());
+        }
+    }
+}
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
index 6f83bf3..d709acf 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
@@ -89,6 +89,6 @@
 
     public void readFromParcel(Parcel source) {
         mSdp = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
index 461f8bf..559d61b 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
@@ -147,8 +147,8 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
index 3242081..160f9eb 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
@@ -180,7 +180,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
index ec8b6bf..f0ee5f3 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
@@ -105,6 +105,6 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mContactUri = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
index 7e22106..8fbb000c 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
@@ -146,8 +146,8 @@
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
         mRequestId = source.readInt();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
     }
 
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
index 2f797b4..954c2b6 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
@@ -122,6 +122,6 @@
     public void readFromParcel(Parcel source) {
         mResUri = source.readString();
         mDisplayName = source.readString();
-        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader());
+        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader(), com.android.ims.internal.uce.presence.PresResInstanceInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
index e33aa13..63247db 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
@@ -236,7 +236,7 @@
         mListName = source.readString();
         mRequestId = source.readInt();
         mPresSubscriptionState = source.readParcelable(
-                                  PresSubscriptionState.class.getClassLoader());
+                                  PresSubscriptionState.class.getClassLoader(), com.android.ims.internal.uce.presence.PresSubscriptionState.class);
         mSubscriptionExpireTime = source.readInt();
         mSubscriptionTerminatedReason = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
index 5e394ef..8097a37 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
@@ -185,7 +185,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 14fd4c2..40ca9fb 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -90,6 +90,14 @@
     public static final ComponentName ACCESSIBILITY_BUTTON_COMPONENT_NAME =
             new ComponentName("com.android.server.accessibility", "AccessibilityButton");
 
+    public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "ColorInversionTile");
+    public static final ComponentName DALTONIZER_TILE_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "ColorCorrectionTile");
+    public static final ComponentName ONE_HANDED_TILE_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "OneHandedModeTile");
+    public static final ComponentName REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "ReduceBrightColorsTile");
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index b723db2..4ad232a 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -467,8 +467,21 @@
     }
 
     protected void showEmptyState(ResolverListAdapter activeListAdapter,
+            @DrawableRes int iconRes, String title, String subtitle) {
+        showEmptyState(activeListAdapter, iconRes, title, subtitle, /* buttonOnClick */ null);
+    }
+
+    protected void showEmptyState(ResolverListAdapter activeListAdapter,
             @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes,
             View.OnClickListener buttonOnClick) {
+        String title = titleRes == 0 ? null : mContext.getString(titleRes);
+        String subtitle = subtitleRes == 0 ? null : mContext.getString(subtitleRes);
+        showEmptyState(activeListAdapter, iconRes, title, subtitle, buttonOnClick);
+    }
+
+    protected void showEmptyState(ResolverListAdapter activeListAdapter,
+            @DrawableRes int iconRes, String title, String subtitle,
+            View.OnClickListener buttonOnClick) {
         ProfileDescriptor descriptor = getItem(
                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
         descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
@@ -479,15 +492,15 @@
         View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
         setupContainerPadding(container);
 
-        TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
-        title.setText(titleRes);
+        TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
+        titleView.setText(title);
 
-        TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
-        if (subtitleRes != 0) {
-            subtitle.setVisibility(View.VISIBLE);
-            subtitle.setText(subtitleRes);
+        TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
+        if (subtitle != null) {
+            subtitleView.setVisibility(View.VISIBLE);
+            subtitleView.setText(subtitle);
         } else {
-            subtitle.setVisibility(View.GONE);
+            subtitleView.setVisibility(View.GONE);
         }
 
         Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 3b6a877..393bff4 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -16,7 +16,17 @@
 
 package com.android.internal.app;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.os.UserHandle;
 import android.view.LayoutInflater;
@@ -184,8 +194,8 @@
             View.OnClickListener listener) {
         showEmptyState(activeListAdapter,
                 R.drawable.ic_work_apps_off,
-                R.string.resolver_turn_on_work_apps,
-                /* subtitleRes */ 0,
+                getWorkAppPausedTitle(),
+                /* subtitle = */ null,
                 listener);
     }
 
@@ -194,13 +204,13 @@
         if (mIsSendAction) {
             showEmptyState(activeListAdapter,
                     R.drawable.ic_sharing_disabled,
-                    R.string.resolver_cross_profile_blocked,
-                    R.string.resolver_cant_share_with_work_apps_explanation);
+                    getCrossProfileBlockedTitle(),
+                    getCantShareWithWorkMessage());
         } else {
             showEmptyState(activeListAdapter,
                     R.drawable.ic_sharing_disabled,
-                    R.string.resolver_cross_profile_blocked,
-                    R.string.resolver_cant_access_work_apps_explanation);
+                    getCrossProfileBlockedTitle(),
+                    getCantAccessWorkMessage());
         }
     }
 
@@ -209,13 +219,13 @@
         if (mIsSendAction) {
             showEmptyState(activeListAdapter,
                     R.drawable.ic_sharing_disabled,
-                    R.string.resolver_cross_profile_blocked,
-                    R.string.resolver_cant_share_with_personal_apps_explanation);
+                    getCrossProfileBlockedTitle(),
+                    getCantShareWithPersonalMessage());
         } else {
             showEmptyState(activeListAdapter,
                     R.drawable.ic_sharing_disabled,
-                    R.string.resolver_cross_profile_blocked,
-                    R.string.resolver_cant_access_personal_apps_explanation);
+                    getCrossProfileBlockedTitle(),
+                    getCantAccessPersonalMessage());
         }
     }
 
@@ -223,8 +233,8 @@
     protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
         showEmptyState(listAdapter,
                 R.drawable.ic_no_apps,
-                R.string.resolver_no_personal_apps_available,
-                /* subtitleRes */ 0);
+                getNoPersonalAppsAvailableMessage(),
+                /* subtitle= */ null);
 
     }
 
@@ -232,10 +242,65 @@
     protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
         showEmptyState(listAdapter,
                 R.drawable.ic_no_apps,
-                R.string.resolver_no_work_apps_available,
-                /* subtitleRes */ 0);
+                getNoWorkAppsAvailableMessage(),
+                /* subtitle = */ null);
     }
 
+    private String getWorkAppPausedTitle() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_WORK_PAUSED_TITLE,
+                () -> getContext().getString(R.string.resolver_turn_on_work_apps));
+    }
+
+    private String getCrossProfileBlockedTitle() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                () -> getContext().getString(R.string.resolver_cross_profile_blocked));
+    }
+
+    private String getCantShareWithWorkMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_SHARE_WITH_WORK,
+                () -> getContext().getString(
+                        R.string.resolver_cant_share_with_work_apps_explanation));
+    }
+
+    private String getCantShareWithPersonalMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_SHARE_WITH_PERSONAL,
+                () -> getContext().getString(
+                        R.string.resolver_cant_share_with_personal_apps_explanation));
+    }
+
+    private String getCantAccessWorkMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_ACCESS_WORK,
+                () -> getContext().getString(
+                        R.string.resolver_cant_access_work_apps_explanation));
+    }
+
+    private String getCantAccessPersonalMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_ACCESS_PERSONAL,
+                () -> getContext().getString(
+                        R.string.resolver_cant_access_personal_apps_explanation));
+    }
+
+    private String getNoWorkAppsAvailableMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_NO_WORK_APPS,
+                () -> getContext().getString(
+                        R.string.resolver_no_work_apps_available));
+    }
+
+    private String getNoPersonalAppsAvailableMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_NO_PERSONAL_APPS,
+                () -> getContext().getString(
+                        R.string.resolver_no_personal_apps_available));
+    }
+
+
     void setEmptyStateBottomOffset(int bottomOffset) {
         mBottomOffset = bottomOffset;
     }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 587876d..9648008 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.BluetoothBatteryStats;
 import android.os.ParcelFileDescriptor;
 import android.os.WakeLockStats;
 import android.os.WorkSource;
@@ -162,6 +163,10 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)")
     WakeLockStats getWakeLockStats();
 
+    /** {@hide} */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)")
+    BluetoothBatteryStats getBluetoothBatteryStats();
+
     HealthStatsParceler takeUidSnapshot(int uid);
     HealthStatsParceler[] takeUidSnapshots(in int[] uid);
 
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 0f37dc5..25b8dba 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -16,13 +16,14 @@
 
 package com.android.internal.app;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 
 import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
 import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
 
 import android.annotation.Nullable;
-import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -101,16 +102,16 @@
         Intent intentReceived = getIntent();
         String className = intentReceived.getComponent().getClassName();
         final int targetUserId;
-        final int userMessageId;
+        final String userMessage;
         if (className.equals(FORWARD_INTENT_TO_PARENT)) {
-            userMessageId = com.android.internal.R.string.forward_intent_to_owner;
+            userMessage = getForwardToPersonalMessage();
             targetUserId = getProfileParent();
 
             getMetricsLogger().write(
                     new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE)
                     .setSubtype(MetricsEvent.PARENT_PROFILE));
         } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) {
-            userMessageId = com.android.internal.R.string.forward_intent_to_work;
+            userMessage = getForwardToWorkMessage();
             targetUserId = getManagedProfile();
 
             getMetricsLogger().write(
@@ -118,7 +119,7 @@
                     .setSubtype(MetricsEvent.MANAGED_PROFILE));
         } else {
             Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly");
-            userMessageId = -1;
+            userMessage = null;
             targetUserId = UserHandle.USER_NULL;
         }
         if (targetUserId == UserHandle.USER_NULL) {
@@ -156,11 +157,23 @@
                     return targetResolveInfo;
                 }, mExecutorService)
                 .thenAcceptAsync(result -> {
-                    maybeShowDisclosure(intentReceived, result, userMessageId);
+                    maybeShowDisclosure(intentReceived, result, userMessage);
                     finish();
                 }, getApplicationContext().getMainExecutor());
     }
 
+    private String getForwardToPersonalMessage() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                FORWARD_INTENT_TO_PERSONAL,
+                () -> getString(com.android.internal.R.string.forward_intent_to_owner));
+    }
+
+    private String getForwardToWorkMessage() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                FORWARD_INTENT_TO_WORK,
+                () -> getString(com.android.internal.R.string.forward_intent_to_work));
+    }
+
     private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) {
         if (resolveInfo == null) {
             return false;
@@ -183,9 +196,9 @@
     }
 
     private void maybeShowDisclosure(
-            Intent intentReceived, ResolveInfo resolveInfo, int messageId) {
-        if (shouldShowDisclosure(resolveInfo, intentReceived)) {
-            mInjector.showToast(messageId, Toast.LENGTH_LONG);
+            Intent intentReceived, ResolveInfo resolveInfo, @Nullable String message) {
+        if (shouldShowDisclosure(resolveInfo, intentReceived) && message != null) {
+            mInjector.showToast(message, Toast.LENGTH_LONG);
         }
     }
 
@@ -405,8 +418,8 @@
         }
 
         @Override
-        public void showToast(int messageId, int duration) {
-            Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show();
+        public void showToast(String message, int duration) {
+            Toast.makeText(IntentForwarderActivity.this, message, duration).show();
         }
     }
 
@@ -419,6 +432,6 @@
 
         CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId);
 
-        void showToast(@StringRes int messageId, int duration);
+        void showToast(String message, int duration);
     }
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f9a8c7b..347153c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -17,6 +17,13 @@
 package com.android.internal.app;
 
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.PermissionChecker.PID_UNKNOWN;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -32,6 +39,7 @@
 import android.app.VoiceInteractor.PickOptionRequest.Option;
 import android.app.VoiceInteractor.Prompt;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -123,7 +131,7 @@
     protected View mProfileView;
     private int mLastSelected = AbsListView.INVALID_POSITION;
     private boolean mResolvingHome = false;
-    private int mProfileSwitchMessageId = -1;
+    private String mProfileSwitchMessage;
     private int mLayoutId;
     @VisibleForTesting
     protected final ArrayList<Intent> mIntents = new ArrayList<>();
@@ -441,7 +449,7 @@
 
         // Determine whether we should show that intent is forwarded
         // from managed profile to owner or other way around.
-        setProfileSwitchMessageId(intent.getContentUserHint());
+        setProfileSwitchMessage(intent.getContentUserHint());
 
         mLaunchedFromUid = getLaunchedFromUid();
         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
@@ -674,7 +682,7 @@
         }
 
         // Do not show the profile switch message anymore.
-        mProfileSwitchMessageId = -1;
+        mProfileSwitchMessage = null;
 
         onTargetSelected(dri, false);
         if (!mAwaitingDelegateResponse) {
@@ -828,7 +836,7 @@
         }
     }
 
-    private void setProfileSwitchMessageId(int contentUserHint) {
+    private void setProfileSwitchMessage(int contentUserHint) {
         if (contentUserHint != UserHandle.USER_CURRENT &&
                 contentUserHint != UserHandle.myUserId()) {
             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
@@ -837,13 +845,25 @@
                     : false;
             boolean targetIsManaged = userManager.isManagedProfile();
             if (originIsManaged && !targetIsManaged) {
-                mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner;
+                mProfileSwitchMessage = getForwardToPersonalMsg();
             } else if (!originIsManaged && targetIsManaged) {
-                mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work;
+                mProfileSwitchMessage = getForwardToWorkMsg();
             }
         }
     }
 
+    private String getForwardToPersonalMsg() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                FORWARD_INTENT_TO_PERSONAL,
+                () -> getString(com.android.internal.R.string.forward_intent_to_owner));
+    }
+
+    private String getForwardToWorkMsg() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                FORWARD_INTENT_TO_WORK,
+                () -> getString(com.android.internal.R.string.forward_intent_to_work));
+    }
+
     /**
      * Turn on launch mode that is safe to use when forwarding intents received from
      * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
@@ -1095,9 +1115,9 @@
         ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
                 .resolveInfoForPosition(which, hasIndexBeenFiltered);
         if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
-            Toast.makeText(this, String.format(getResources().getString(
-                    com.android.internal.R.string.activity_resolver_work_profiles_support),
-                    ri.activityInfo.loadLabel(getPackageManager()).toString()),
+            Toast.makeText(this,
+                    getWorkProfileNotSupportedMsg(
+                            ri.activityInfo.loadLabel(getPackageManager()).toString()),
                     Toast.LENGTH_LONG).show();
             return;
         }
@@ -1128,6 +1148,15 @@
         }
     }
 
+    private String getWorkProfileNotSupportedMsg(String launcherName) {
+        return getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_WORK_PROFILE_NOT_SUPPORTED,
+                () -> getString(
+                        com.android.internal.R.string.activity_resolver_work_profiles_support,
+                        launcherName),
+                launcherName);
+    }
+
     /**
      * Replace me in subclasses!
      */
@@ -1394,8 +1423,8 @@
         }
         // If needed, show that intent is forwarded
         // from managed profile to owner or other way around.
-        if (mProfileSwitchMessageId != -1) {
-            Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show();
+        if (mProfileSwitchMessage != null) {
+            Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
         }
         if (!mSafeForwardingMode) {
             if (cti.startAsUser(this, null, user)) {
@@ -1742,12 +1771,12 @@
         viewPager.setSaveEnabled(false);
         TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
                 .setContent(R.id.profile_pager)
-                .setIndicator(getString(R.string.resolver_personal_tab));
+                .setIndicator(getPersonalTabLabel());
         tabHost.addTab(tabSpec);
 
         tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
                 .setContent(R.id.profile_pager)
-                .setIndicator(getString(R.string.resolver_work_tab));
+                .setIndicator(getWorkTabLabel());
         tabHost.addTab(tabSpec);
 
         TabWidget tabWidget = tabHost.getTabWidget();
@@ -1799,6 +1828,16 @@
         findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
     }
 
+    private String getPersonalTabLabel() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab));
+    }
+
+    private String getWorkTabLabel() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab));
+    }
+
     void onHorizontalSwipeStateChanged(int state) {}
 
     private void maybeHideDivider() {
@@ -1830,8 +1869,6 @@
     }
 
     private void resetTabsHeaderStyle(TabWidget tabWidget) {
-        String workContentDescription = getString(R.string.resolver_work_tab_accessibility);
-        String personalContentDescription = getString(R.string.resolver_personal_tab_accessibility);
         for (int i = 0; i < tabWidget.getChildCount(); i++) {
             View tabView = tabWidget.getChildAt(i);
             TextView title = tabView.findViewById(android.R.id.title);
@@ -1839,14 +1876,26 @@
             title.setTextColor(getAttrColor(this, android.R.attr.textColorTertiary));
             title.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                     getResources().getDimension(R.dimen.resolver_tab_text_size));
-            if (title.getText().equals(getString(R.string.resolver_personal_tab))) {
-                tabView.setContentDescription(personalContentDescription);
-            } else if (title.getText().equals(getString(R.string.resolver_work_tab))) {
-                tabView.setContentDescription(workContentDescription);
+            if (title.getText().equals(getPersonalTabLabel())) {
+                tabView.setContentDescription(getPersonalTabAccessibilityLabel());
+            } else if (title.getText().equals(getWorkTabLabel())) {
+                tabView.setContentDescription(getWorkTabAccessibilityLabel());
             }
         }
     }
 
+    private String getPersonalTabAccessibilityLabel() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_PERSONAL_TAB_ACCESSIBILITY,
+                () -> getString(R.string.resolver_personal_tab_accessibility));
+    }
+
+    private String getWorkTabAccessibilityLabel() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_WORK_TAB_ACCESSIBILITY,
+                () -> getString(R.string.resolver_work_tab_accessibility));
+    }
+
     private static int getAttrColor(Context context, int attr) {
         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
         int colorAccent = ta.getColor(0, 0);
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 622f166..4da59a3 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -16,7 +16,15 @@
 
 package com.android.internal.app;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+
 import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.UserHandle;
@@ -196,8 +204,8 @@
             View.OnClickListener listener) {
         showEmptyState(activeListAdapter,
                 R.drawable.ic_work_apps_off,
-                R.string.resolver_turn_on_work_apps,
-                /* subtitleRes */ 0,
+                getWorkAppPausedTitle(),
+                /* subtitle = */ null,
                 listener);
     }
 
@@ -205,32 +213,72 @@
     protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
         showEmptyState(activeListAdapter,
                 R.drawable.ic_sharing_disabled,
-                R.string.resolver_cross_profile_blocked,
-                R.string.resolver_cant_access_work_apps_explanation);
+                getCrossProfileBlockedTitle(),
+                getCantAccessWorkMessage());
     }
 
     @Override
     protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
         showEmptyState(activeListAdapter,
                 R.drawable.ic_sharing_disabled,
-                R.string.resolver_cross_profile_blocked,
-                R.string.resolver_cant_access_personal_apps_explanation);
+                getCrossProfileBlockedTitle(),
+                getCantAccessPersonalMessage());
     }
 
     @Override
     protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
         showEmptyState(listAdapter,
                 R.drawable.ic_no_apps,
-                R.string.resolver_no_personal_apps_available,
-                /* subtitleRes */ 0);
+                getNoPersonalAppsAvailableMessage(),
+                /* subtitle = */ null);
     }
 
     @Override
     protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
         showEmptyState(listAdapter,
                 R.drawable.ic_no_apps,
-                R.string.resolver_no_work_apps_available,
-                /* subtitleRes */ 0);
+                getNoWorkAppsAvailableMessage(),
+                /* subtitle= */ null);
+    }
+
+    private String getWorkAppPausedTitle() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_WORK_PAUSED_TITLE,
+                () -> getContext().getString(R.string.resolver_turn_on_work_apps));
+    }
+
+    private String getCrossProfileBlockedTitle() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+                () -> getContext().getString(R.string.resolver_cross_profile_blocked));
+    }
+
+    private String getCantAccessWorkMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_ACCESS_WORK,
+                () -> getContext().getString(
+                        R.string.resolver_cant_access_work_apps_explanation));
+    }
+
+    private String getCantAccessPersonalMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_CANT_ACCESS_PERSONAL,
+                () -> getContext().getString(
+                        R.string.resolver_cant_access_personal_apps_explanation));
+    }
+
+    private String getNoWorkAppsAvailableMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_NO_WORK_APPS,
+                () -> getContext().getString(
+                        R.string.resolver_no_work_apps_available));
+    }
+
+    private String getNoPersonalAppsAvailableMessage() {
+        return getContext().getSystemService(DevicePolicyManager.class).getString(
+                RESOLVER_NO_PERSONAL_APPS,
+                () -> getContext().getString(
+                        R.string.resolver_no_personal_apps_available));
     }
 
     void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index ca0856238..3531fad 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -16,11 +16,14 @@
 
 package com.android.internal.app;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -70,8 +73,8 @@
         String dialogTitle;
         String dialogMessage = null;
         if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE) {
-            dialogTitle = getResources().getString(R.string.work_mode_off_title);
-            dialogMessage = getResources().getString(R.string.work_mode_off_message);
+            dialogTitle = getDialogTitle();
+            dialogMessage = getDialogMessage();
         } else {
             Log.wtf(TAG, "Invalid unlaunchable type: " + mReason);
             finish();
@@ -91,6 +94,17 @@
         builder.show();
     }
 
+    private String getDialogTitle() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title));
+    }
+
+    private String getDialogMessage() {
+        return getSystemService(DevicePolicyManager.class).getString(
+                UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE,
+                () -> getString(R.string.work_mode_off_message));
+    }
+
     @Override
     public void onDismiss(DialogInterface dialog) {
         finish();
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 9c3c224..301de2d 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -237,12 +237,12 @@
     private DisplayResolveInfo(Parcel in) {
         mDisplayLabel = in.readCharSequence();
         mExtendedInfo = in.readCharSequence();
-        mResolvedIntent = in.readParcelable(null /* ClassLoader */);
+        mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class);
         mSourceIntents.addAll(
                 Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */,
                         Intent.class)));
         mIsSuspended = in.readBoolean();
         mPinned = in.readBoolean();
-        mResolveInfo = in.readParcelable(null /* ClassLoader */);
+        mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class);
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 13a39de..0ada13a7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -163,6 +163,12 @@
     public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED =
             "location_indicators_small_enabled";
 
+    /**
+     * Whether to show the location indicator for system apps.
+     */
+    public static final String PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM =
+            "location_indicators_show_system";
+
     // Flags related to Assistant
 
     /**
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index 537e797..dff9551 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -62,7 +62,10 @@
         return Color.argb(a, r, g, b);
     }
 
-    private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
+    /**
+     * Returns the composite alpha of the given foreground and background alpha.
+     */
+    public static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) {
         return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
     }
 
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/midi/MidiFramer.java b/core/java/com/android/internal/midi/MidiFramer.java
index 62517fa..bf23ad1 100644
--- a/core/java/com/android/internal/midi/MidiFramer.java
+++ b/core/java/com/android/internal/midi/MidiFramer.java
@@ -99,6 +99,12 @@
                 }
             } else { // data byte
                 if (!mInSysEx) {
+                    // Hack to avoid crashing if we start parsing in the middle
+                    // of a data stream
+                    if (mNeeded <= 0) {
+                        break;
+                    }
+
                     mBuffer[mCount++] = currentByte;
                     if (--mNeeded == 0) {
                         if (mRunningStatus != 0) {
diff --git a/core/java/com/android/internal/midi/OWNERS b/core/java/com/android/internal/midi/OWNERS
new file mode 100644
index 0000000..af273a6
--- /dev/null
+++ b/core/java/com/android/internal/midi/OWNERS
@@ -0,0 +1 @@
+include /services/midi/OWNERS
diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java
index 43984b5..b3bc93a 100644
--- a/core/java/com/android/internal/net/LegacyVpnInfo.java
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.java
@@ -69,7 +69,7 @@
             LegacyVpnInfo info = new LegacyVpnInfo();
             info.key = in.readString();
             info.state = in.readInt();
-            info.intent = in.readParcelable(null);
+            info.intent = in.readParcelable(null, android.app.PendingIntent.class);
             return info;
         }
 
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 2ae56f8..b579be0 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -208,7 +208,7 @@
             config.searchDomains = in.createStringArrayList();
             config.allowedApplications = in.createStringArrayList();
             config.disallowedApplications = in.createStringArrayList();
-            config.configureIntent = in.readParcelable(null);
+            config.configureIntent = in.readParcelable(null, android.app.PendingIntent.class);
             config.startTime = in.readLong();
             config.legacy = in.readInt() != 0;
             config.blocking = in.readInt() != 0;
@@ -217,7 +217,7 @@
             config.allowIPv6 = in.readInt() != 0;
             config.isMetered = in.readInt() != 0;
             config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
-            config.proxyInfo = in.readParcelable(null);
+            config.proxyInfo = in.readParcelable(null, android.net.ProxyInfo.class);
             return config;
         }
 
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index d8dc143..519faa8 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -182,9 +182,9 @@
         ipsecCaCert = in.readString();
         ipsecServerCert = in.readString();
         saveLogin = in.readInt() != 0;
-        proxy = in.readParcelable(null);
+        proxy = in.readParcelable(null, android.net.ProxyInfo.class);
         mAllowedAlgorithms = new ArrayList<>();
-        in.readList(mAllowedAlgorithms, null);
+        in.readList(mAllowedAlgorithms, null, java.lang.String.class);
         isBypassable = in.readBoolean();
         isMetered = in.readBoolean();
         maxMtu = in.readInt();
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 2f40d3b..3b6f8f6 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -14,10 +14,13 @@
 
 package com.android.internal.notification;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN;
+
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
@@ -143,7 +146,7 @@
 
         final NotificationChannel deviceAdmin = new NotificationChannel(
                 DEVICE_ADMIN,
-                context.getString(R.string.notification_channel_device_admin),
+                getDeviceAdminNotificationChannelName(context),
                 NotificationManager.IMPORTANCE_HIGH);
         channelsList.add(deviceAdmin);
 
@@ -209,6 +212,12 @@
         nm.createNotificationChannels(channelsList);
     }
 
+    private static String getDeviceAdminNotificationChannelName(Context context) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(NOTIFICATION_CHANNEL_DEVICE_ADMIN,
+                () -> context.getString(R.string.notification_channel_device_admin));
+    }
+
     /** Remove notification channels which are no longer used */
     public static void removeDeprecated(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java
index 04d7211..5404fea 100644
--- a/core/java/com/android/internal/os/AppFuseMount.java
+++ b/core/java/com/android/internal/os/AppFuseMount.java
@@ -57,7 +57,7 @@
             new Parcelable.Creator<AppFuseMount>() {
         @Override
         public AppFuseMount createFromParcel(Parcel in) {
-            return new AppFuseMount(in.readInt(), in.readParcelable(null));
+            return new AppFuseMount(in.readInt(), in.readParcelable(null, android.os.ParcelFileDescriptor.class));
         }
 
         @Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index a4183ca..8213c86 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -45,6 +45,7 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.BluetoothBatteryStats;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBatteryPropertiesRegistrar;
@@ -84,7 +85,6 @@
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.MutableInt;
-import android.util.Pools;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.util.Slog;
@@ -137,9 +137,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Queue;
-import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
@@ -265,6 +263,7 @@
             MeasuredEnergyStats.POWER_BUCKET_CPU,
             MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
             MeasuredEnergyStats.POWER_BUCKET_WIFI,
+            MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
     };
 
     // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
@@ -1198,6 +1197,48 @@
         return new WakeLockStats(uidWakeLockStats);
     }
 
+    @Override
+    @GuardedBy("this")
+    public BluetoothBatteryStats getBluetoothBatteryStats() {
+        final long elapsedRealtimeUs = mClock.elapsedRealtime() * 1000;
+        ArrayList<BluetoothBatteryStats.UidStats> uidStats = new ArrayList<>();
+        for (int i = mUidStats.size() - 1; i >= 0; i--) {
+            final Uid uid = mUidStats.valueAt(i);
+            final Timer scanTimer = uid.getBluetoothScanTimer();
+            final long scanTimeMs =
+                    scanTimer != null ? scanTimer.getTotalTimeLocked(
+                            elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0;
+
+            final Timer unoptimizedScanTimer = uid.getBluetoothUnoptimizedScanTimer();
+            final long unoptimizedScanTimeMs =
+                    unoptimizedScanTimer != null ? unoptimizedScanTimer.getTotalTimeLocked(
+                            elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0;
+
+            final Counter scanResultCounter = uid.getBluetoothScanResultCounter();
+            final int scanResultCount =
+                    scanResultCounter != null ? scanResultCounter.getCountLocked(
+                            STATS_SINCE_CHARGED) : 0;
+
+            final ControllerActivityCounter counter = uid.getBluetoothControllerActivity();
+            final long rxTimeMs =  counter != null ? counter.getRxTimeCounter().getCountLocked(
+                    STATS_SINCE_CHARGED) : 0;
+            final long txTimeMs =  counter != null ? counter.getTxTimeCounters()[0].getCountLocked(
+                    STATS_SINCE_CHARGED) : 0;
+
+            if (scanTimeMs != 0 || unoptimizedScanTimeMs != 0 || scanResultCount != 0
+                    || rxTimeMs != 0 || txTimeMs != 0) {
+                uidStats.add(new BluetoothBatteryStats.UidStats(uid.getUid(),
+                        scanTimeMs,
+                        unoptimizedScanTimeMs,
+                        scanResultCount,
+                        rxTimeMs,
+                        txTimeMs));
+            }
+        }
+
+        return new BluetoothBatteryStats(uidStats);
+    }
+
     String mLastWakeupReason = null;
     long mLastWakeupUptimeMs = 0;
     private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
@@ -8371,6 +8412,11 @@
             if (wifiControllerActivity != null) {
                 wifiControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs);
             }
+            final ControllerActivityCounterImpl bluetoothControllerActivity =
+                    getBluetoothControllerActivity();
+            if (bluetoothControllerActivity != null) {
+                bluetoothControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs);
+            }
             final MeasuredEnergyStats energyStats =
                     getOrCreateMeasuredEnergyStatsIfSupportedLocked();
             if (energyStats != null) {
@@ -8718,7 +8764,7 @@
         }
 
         @Override
-        public ControllerActivityCounter getBluetoothControllerActivity() {
+        public ControllerActivityCounterImpl getBluetoothControllerActivity() {
             return mBluetoothControllerActivity;
         }
 
@@ -8839,6 +8885,14 @@
 
         @GuardedBy("mBsi")
         @Override
+        public long getBluetoothMeasuredBatteryConsumptionUC(
+                @BatteryConsumer.ProcessState int processState) {
+            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
+                    processState);
+        }
+
+        @GuardedBy("mBsi")
+        @Override
         public long getCpuMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
         }
@@ -11424,6 +11478,13 @@
                     wifiControllerActivity.setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 }
 
+                final ControllerActivityCounterImpl bluetoothControllerActivity =
+                        getBluetoothControllerActivity();
+                if (bluetoothControllerActivity != null) {
+                    bluetoothControllerActivity.setState(batteryConsumerProcessState,
+                            elapsedRealtimeMs);
+                }
+
                 final MeasuredEnergyStats energyStats =
                         getOrCreateMeasuredEnergyStatsIfSupportedLocked();
                 if (energyStats != null) {
@@ -12669,8 +12730,6 @@
         }
     }
 
-    private final Pools.Pool<NetworkStats> mNetworkStatsPool = new Pools.SynchronizedPool<>(6);
-
     private final Object mWifiNetworkLock = new Object();
 
     @GuardedBy("mWifiNetworkLock")
@@ -12688,13 +12747,15 @@
     private NetworkStats mLastModemNetworkStats = new NetworkStats(0, -1);
 
     @VisibleForTesting
-    protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager,
-            String[] ifaces) {
-        Objects.requireNonNull(networkStatsManager);
-        if (!ArrayUtils.isEmpty(ifaces)) {
-            return networkStatsManager.getDetailedUidStats(Set.of(ifaces));
-        }
-        return null;
+    protected NetworkStats readMobileNetworkStatsLocked(
+            @NonNull NetworkStatsManager networkStatsManager) {
+        return networkStatsManager.getMobileUidStats();
+    }
+
+    @VisibleForTesting
+    protected NetworkStats readWifiNetworkStatsLocked(
+            @NonNull NetworkStatsManager networkStatsManager) {
+        return networkStatsManager.getWifiUidStats();
     }
 
     /**
@@ -12714,21 +12775,15 @@
         // Grab a separate lock to acquire the network stats, which may do I/O.
         NetworkStats delta = null;
         synchronized (mWifiNetworkLock) {
-            final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager,
-                    mWifiIfaces);
+            final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager);
             if (latestStats != null) {
-                delta = NetworkStats.subtract(latestStats, mLastWifiNetworkStats, null, null,
-                        mNetworkStatsPool.acquire());
-                mNetworkStatsPool.release(mLastWifiNetworkStats);
+                delta = latestStats.subtract(mLastWifiNetworkStats);
                 mLastWifiNetworkStats = latestStats;
             }
         }
 
         synchronized (this) {
             if (!mOnBatteryInternal || mIgnoreNextExternalStats) {
-                if (delta != null) {
-                    mNetworkStatsPool.release(delta);
-                }
                 if (mIgnoreNextExternalStats) {
                     // TODO: Strictly speaking, we should re-mark all 5 timers for each uid (and the
                     //  global one) here like we do for display. But I'm not sure it's worth the
@@ -12746,63 +12801,63 @@
 
             SparseLongArray rxPackets = new SparseLongArray();
             SparseLongArray txPackets = new SparseLongArray();
+            SparseLongArray rxTimesMs = new SparseLongArray();
+            SparseLongArray txTimesMs = new SparseLongArray();
             long totalTxPackets = 0;
             long totalRxPackets = 0;
             if (delta != null) {
-                NetworkStats.Entry entry = new NetworkStats.Entry();
-                final int size = delta.size();
-                for (int i = 0; i < size; i++) {
-                    entry = delta.getValues(i, entry);
-
+                for (NetworkStats.Entry entry : delta) {
                     if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
-                                + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets
-                                + " txPackets=" + entry.txPackets);
+                        Slog.d(TAG, "Wifi uid " + entry.getUid()
+                                + ": delta rx=" + entry.getRxBytes()
+                                + " tx=" + entry.getTxBytes()
+                                + " rxPackets=" + entry.getRxPackets()
+                                + " txPackets=" + entry.getTxPackets());
                     }
 
-                    if (entry.rxBytes == 0 && entry.txBytes == 0) {
+                    if (entry.getRxBytes() == 0 && entry.getTxBytes() == 0) {
                         // Skip the lookup below since there is no work to do.
                         continue;
                     }
 
-                    final int uid = mapUid(entry.uid);
+                    final int uid = mapUid(entry.getUid());
                     final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs);
-                    if (entry.rxBytes != 0) {
-                        u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
-                                entry.rxPackets);
-                        if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
-                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes,
-                                    entry.rxPackets);
+                    if (entry.getRxBytes() != 0) {
+                        u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.getRxBytes(),
+                                entry.getRxPackets());
+                        if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers
+                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.getRxBytes(),
+                                    entry.getRxPackets());
                         }
                         mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
-                                entry.rxBytes);
+                                entry.getRxBytes());
                         mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
-                                entry.rxPackets);
+                                entry.getRxPackets());
 
-                        add(rxPackets, uid, entry.rxPackets);
+                        rxPackets.incrementValue(uid, entry.getRxPackets());
 
                         // Sum the total number of packets so that the Rx Power can
                         // be evenly distributed amongst the apps.
-                        totalRxPackets += entry.rxPackets;
+                        totalRxPackets += entry.getRxPackets();
                     }
 
-                    if (entry.txBytes != 0) {
-                        u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
-                                entry.txPackets);
-                        if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers
-                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes,
-                                    entry.txPackets);
+                    if (entry.getTxBytes() != 0) {
+                        u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.getTxBytes(),
+                                entry.getTxPackets());
+                        if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers
+                            u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.getTxBytes(),
+                                    entry.getTxPackets());
                         }
                         mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
-                                entry.txBytes);
+                                entry.getTxBytes());
                         mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
-                                entry.txPackets);
+                                entry.getTxPackets());
 
-                        add(txPackets, uid, entry.txPackets);
+                        txPackets.incrementValue(uid, entry.getTxPackets());
 
                         // Sum the total number of packets so that the Tx Power can
                         // be evenly distributed amongst the apps.
-                        totalTxPackets += entry.txPackets;
+                        totalTxPackets += entry.getTxPackets();
                     }
 
                     // Calculate consumed energy for this uid. Only do so if WifiReporting isn't
@@ -12828,13 +12883,12 @@
                             }
                         }
 
-                        uidEstimatedConsumptionMah.add(u.getUid(),
+                        uidEstimatedConsumptionMah.incrementValue(u.getUid(),
                                 mWifiPowerCalculator.calcPowerWithoutControllerDataMah(
-                                        entry.rxPackets, entry.txPackets,
+                                        entry.getRxPackets(), entry.getTxPackets(),
                                         uidRunningMs, uidScanMs, uidBatchScanMs));
                     }
                 }
-                mNetworkStatsPool.release(delta);
                 delta = null;
             }
 
@@ -12923,12 +12977,9 @@
                                     + scanTxTimeSinceMarkMs + " ms)");
                         }
 
-                        ControllerActivityCounterImpl activityCounter =
-                                uid.getOrCreateWifiControllerActivityLocked();
-                        activityCounter.getOrCreateRxTimeCounter()
-                                .increment(scanRxTimeSinceMarkMs, elapsedRealtimeMs);
-                        activityCounter.getOrCreateTxTimeCounters()[0]
-                                .increment(scanTxTimeSinceMarkMs, elapsedRealtimeMs);
+                        rxTimesMs.incrementValue(uid.getUid(), scanRxTimeSinceMarkMs);
+                        txTimesMs.incrementValue(uid.getUid(), scanTxTimeSinceMarkMs);
+
                         leftOverRxTimeMs -= scanRxTimeSinceMarkMs;
                         leftOverTxTimeMs -= scanTxTimeSinceMarkMs;
                     }
@@ -12955,7 +13006,7 @@
                     if (uidEstimatedConsumptionMah != null) {
                         double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah(
                                 scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs);
-                        uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah);
+                        uidEstimatedConsumptionMah.incrementValue(uid.getUid(), uidEstMah);
                     }
                 }
 
@@ -12967,36 +13018,51 @@
                 // Distribute the remaining Tx power appropriately between all apps that transmitted
                 // packets.
                 for (int i = 0; i < txPackets.size(); i++) {
-                    final Uid uid = getUidStatsLocked(txPackets.keyAt(i),
-                            elapsedRealtimeMs, uptimeMs);
+                    final int uid = txPackets.keyAt(i);
                     final long myTxTimeMs = (txPackets.valueAt(i) * leftOverTxTimeMs)
                             / totalTxPackets;
-                    if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "  TxTime for UID " + uid.getUid() + ": " + myTxTimeMs + " ms");
-                    }
-                    uid.getOrCreateWifiControllerActivityLocked().getOrCreateTxTimeCounters()[0]
-                            .increment(myTxTimeMs, elapsedRealtimeMs);
-                    if (uidEstimatedConsumptionMah != null) {
-                        uidEstimatedConsumptionMah.add(uid.getUid(),
-                                mWifiPowerCalculator.calcPowerFromControllerDataMah(
-                                        0, myTxTimeMs, 0));
-                    }
+                    txTimesMs.incrementValue(uid, myTxTimeMs);
                 }
 
                 // Distribute the remaining Rx power appropriately between all apps that received
                 // packets.
                 for (int i = 0; i < rxPackets.size(); i++) {
-                    final Uid uid = getUidStatsLocked(rxPackets.keyAt(i),
-                            elapsedRealtimeMs, uptimeMs);
+                    final int uid = rxPackets.keyAt(i);
                     final long myRxTimeMs = (rxPackets.valueAt(i) * leftOverRxTimeMs)
                             / totalRxPackets;
+                    rxTimesMs.incrementValue(uid, myRxTimeMs);
+                }
+
+                for (int i = 0; i < txTimesMs.size(); i++) {
+                    final int uid = txTimesMs.keyAt(i);
+                    final long myTxTimeMs = txTimesMs.valueAt(i);
                     if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "  RxTime for UID " + uid.getUid() + ": " + myRxTimeMs + " ms");
+                        Slog.d(TAG, "  TxTime for UID " + uid + ": " + myTxTimeMs + " ms");
                     }
-                    uid.getOrCreateWifiControllerActivityLocked().getOrCreateRxTimeCounter()
+                    getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+                            .getOrCreateWifiControllerActivityLocked()
+                            .getOrCreateTxTimeCounters()[0]
+                            .increment(myTxTimeMs, elapsedRealtimeMs);
+                    if (uidEstimatedConsumptionMah != null) {
+                        uidEstimatedConsumptionMah.incrementValue(uid,
+                                mWifiPowerCalculator.calcPowerFromControllerDataMah(
+                                        0, myTxTimeMs, 0));
+                    }
+                }
+
+                for (int i = 0; i < rxTimesMs.size(); i++) {
+                    final int uid = rxTimesMs.keyAt(i);
+                    final long myRxTimeMs = rxTimesMs.valueAt(i);
+                    if (DEBUG_ENERGY) {
+                        Slog.d(TAG, "  RxTime for UID " + uid + ": " + myRxTimeMs + " ms");
+                    }
+
+                    getUidStatsLocked(rxTimesMs.keyAt(i), elapsedRealtimeMs, uptimeMs)
+                            .getOrCreateWifiControllerActivityLocked()
+                            .getOrCreateRxTimeCounter()
                             .increment(myRxTimeMs, elapsedRealtimeMs);
                     if (uidEstimatedConsumptionMah != null) {
-                        uidEstimatedConsumptionMah.add(uid.getUid(),
+                        uidEstimatedConsumptionMah.incrementValue(uid,
                                 mWifiPowerCalculator.calcPowerFromControllerDataMah(
                                         myRxTimeMs, 0, 0));
                     }
@@ -13004,7 +13070,6 @@
 
                 // Any left over power use will be picked up by the WiFi category in BatteryStatsHelper.
 
-
                 // Update WiFi controller stats.
                 mWifiActivity.getOrCreateRxTimeCounter().increment(
                         info.getControllerRxDurationMillis(), elapsedRealtimeMs);
@@ -13083,21 +13148,15 @@
         // Grab a separate lock to acquire the network stats, which may do I/O.
         NetworkStats delta = null;
         synchronized (mModemNetworkLock) {
-            final NetworkStats latestStats = readNetworkStatsLocked(networkStatsManager,
-                    mModemIfaces);
+            final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager);
             if (latestStats != null) {
-                delta = NetworkStats.subtract(latestStats, mLastModemNetworkStats, null, null,
-                        mNetworkStatsPool.acquire());
-                mNetworkStatsPool.release(mLastModemNetworkStats);
+                delta = latestStats.subtract(mLastModemNetworkStats);
                 mLastModemNetworkStats = latestStats;
             }
         }
 
         synchronized (this) {
             if (!mOnBatteryInternal || mIgnoreNextExternalStats) {
-                if (delta != null) {
-                    mNetworkStatsPool.release(delta);
-                }
                 return;
             }
 
@@ -13224,7 +13283,7 @@
                         // Distribute measured mobile radio charge consumption based on app radio
                         // active time
                         if (uidEstimatedConsumptionMah != null) {
-                            uidEstimatedConsumptionMah.add(u.getUid(),
+                            uidEstimatedConsumptionMah.incrementValue(u.getUid(),
                                     mMobileRadioPowerCalculator.calcPowerFromRadioActiveDurationMah(
                                             appRadioTimeUs / 1000));
                         }
@@ -13303,7 +13362,6 @@
                             totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
 
-                mNetworkStatsPool.release(delta);
                 delta = null;
             }
         }
@@ -13349,8 +13407,8 @@
             energy = info.getControllerEnergyUsed();
             if (!info.getUidTraffic().isEmpty()) {
                 for (UidTraffic traffic : info.getUidTraffic()) {
-                    add(uidRxBytes, traffic.getUid(), traffic.getRxBytes());
-                    add(uidTxBytes, traffic.getUid(), traffic.getTxBytes());
+                    uidRxBytes.incrementValue(traffic.getUid(), traffic.getRxBytes());
+                    uidTxBytes.incrementValue(traffic.getUid(), traffic.getTxBytes());
                 }
             }
         }
@@ -13442,6 +13500,9 @@
         long leftOverRxTimeMs = rxTimeMs;
         long leftOverTxTimeMs = txTimeMs;
 
+        final SparseLongArray rxTimesMs = new SparseLongArray(uidCount);
+        final SparseLongArray txTimesMs = new SparseLongArray(uidCount);
+
         for (int i = 0; i < uidCount; i++) {
             final Uid u = mUidStats.valueAt(i);
             if (u.mBluetoothScanTimer == null) {
@@ -13471,15 +13532,11 @@
                     scanTimeTxSinceMarkMs = (txTimeMs * scanTimeTxSinceMarkMs) / totalScanTimeMs;
                 }
 
-                final ControllerActivityCounterImpl counter =
-                        u.getOrCreateBluetoothControllerActivityLocked();
-                counter.getOrCreateRxTimeCounter()
-                        .increment(scanTimeRxSinceMarkMs, elapsedRealtimeMs);
-                counter.getOrCreateTxTimeCounters()[0]
-                        .increment(scanTimeTxSinceMarkMs, elapsedRealtimeMs);
+                rxTimesMs.incrementValue(u.getUid(), scanTimeRxSinceMarkMs);
+                txTimesMs.incrementValue(u.getUid(), scanTimeTxSinceMarkMs);
 
                 if (uidEstimatedConsumptionMah != null) {
-                    uidEstimatedConsumptionMah.add(u.getUid(),
+                    uidEstimatedConsumptionMah.incrementValue(u.getUid(),
                             mBluetoothPowerCalculator.calculatePowerMah(
                                     scanTimeRxSinceMarkMs, scanTimeTxSinceMarkMs, 0));
                 }
@@ -13540,29 +13597,45 @@
 
                 if (totalRxBytes > 0 && rxBytes > 0) {
                     final long timeRxMs = (leftOverRxTimeMs * rxBytes) / totalRxBytes;
-                    if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "UID=" + uid + " rx_bytes=" + rxBytes + " rx_time=" + timeRxMs);
-                    }
-                    counter.getOrCreateRxTimeCounter().increment(timeRxMs, elapsedRealtimeMs);
-
-                    if (uidEstimatedConsumptionMah != null) {
-                        uidEstimatedConsumptionMah.add(u.getUid(),
-                                mBluetoothPowerCalculator.calculatePowerMah(timeRxMs, 0, 0));
-                    }
+                    rxTimesMs.incrementValue(uid, timeRxMs);
                 }
 
                 if (totalTxBytes > 0 && txBytes > 0) {
                     final long timeTxMs = (leftOverTxTimeMs * txBytes) / totalTxBytes;
-                    if (DEBUG_ENERGY) {
-                        Slog.d(TAG, "UID=" + uid + " tx_bytes=" + txBytes + " tx_time=" + timeTxMs);
-                    }
-                    counter.getOrCreateTxTimeCounters()[0]
-                            .increment(timeTxMs, elapsedRealtimeMs);
+                    txTimesMs.incrementValue(uid, timeTxMs);
+                }
+            }
 
-                    if (uidEstimatedConsumptionMah != null) {
-                        uidEstimatedConsumptionMah.add(u.getUid(),
-                                mBluetoothPowerCalculator.calculatePowerMah(0, timeTxMs, 0));
-                    }
+            for (int i = 0; i < txTimesMs.size(); i++) {
+                final int uid = txTimesMs.keyAt(i);
+                final long myTxTimeMs = txTimesMs.valueAt(i);
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "  TxTime for UID " + uid + ": " + myTxTimeMs + " ms");
+                }
+                getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+                        .getOrCreateBluetoothControllerActivityLocked()
+                        .getOrCreateTxTimeCounters()[0]
+                        .increment(myTxTimeMs, elapsedRealtimeMs);
+                if (uidEstimatedConsumptionMah != null) {
+                    uidEstimatedConsumptionMah.incrementValue(uid,
+                            mBluetoothPowerCalculator.calculatePowerMah(0, myTxTimeMs, 0));
+                }
+            }
+
+            for (int i = 0; i < rxTimesMs.size(); i++) {
+                final int uid = rxTimesMs.keyAt(i);
+                final long myRxTimeMs = rxTimesMs.valueAt(i);
+                if (DEBUG_ENERGY) {
+                    Slog.d(TAG, "  RxTime for UID " + uid + ": " + myRxTimeMs + " ms");
+                }
+
+                getUidStatsLocked(rxTimesMs.keyAt(i), elapsedRealtimeMs, uptimeMs)
+                        .getOrCreateBluetoothControllerActivityLocked()
+                        .getOrCreateRxTimeCounter()
+                        .increment(myRxTimeMs, elapsedRealtimeMs);
+                if (uidEstimatedConsumptionMah != null) {
+                    uidEstimatedConsumptionMah.incrementValue(uid,
+                            mBluetoothPowerCalculator.calculatePowerMah(myRxTimeMs, 0, 0));
                 }
             }
         }
@@ -16183,6 +16256,18 @@
         iPw.decreaseIndent();
     }
 
+    /**
+     * Dump Power Profile
+     */
+    @GuardedBy("this")
+    public void dumpPowerProfileLocked(PrintWriter pw) {
+        final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "    ");
+        iPw.printf("Power Profile: \n");
+        iPw.increaseIndent();
+        mPowerProfile.dump(iPw);
+        iPw.decreaseIndent();
+    }
+
     final ReentrantLock mWriteLock = new ReentrantLock();
 
     @GuardedBy("this")
@@ -18140,8 +18225,4 @@
         pw.println();
         dumpMeasuredEnergyStatsLocked(pw);
     }
-
-    private static void add(SparseLongArray array, int key, long delta) {
-        array.put(key, array.get(key) + delta);
-    }
 }
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java
index fd54b32..af82f40 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsStore.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java
@@ -58,6 +58,7 @@
             new BatteryUsageStatsQuery.Builder()
                     .setMaxStatsAgeMs(0)
                     .includePowerModels()
+                    .includeProcessStateData()
                     .build());
     private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats";
     private static final String SNAPSHOT_FILE_EXTENSION = ".bus";
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index 20cf102..e9d55db 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -181,7 +180,7 @@
         }
 
         public Handler getHandler() {
-            return new Handler(Looper.getMainLooper());
+            return BackgroundThread.getHandler();
         }
     }
 
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index c322258..20535d2 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.os;
 
+import android.annotation.Nullable;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryStats.ControllerActivityCounter;
@@ -26,19 +27,33 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.util.Arrays;
 import java.util.List;
 
 public class BluetoothPowerCalculator extends PowerCalculator {
     private static final String TAG = "BluetoothPowerCalc";
     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+
+    private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+
     private final double mIdleMa;
     private final double mRxMa;
     private final double mTxMa;
     private final boolean mHasBluetoothPowerController;
 
     private static class PowerAndDuration {
+        // Return value of BT duration per app
         public long durationMs;
+        // Return value of BT power per app
         public double powerMah;
+
+        public BatteryConsumer.Key[] keys;
+        public double[] powerPerKeyMah;
+
+        // Aggregated BT duration across all apps
+        public long totalDurationMs;
+        // Aggregated BT power across all apps
+        public double totalPowerMah;
     }
 
     public BluetoothPowerCalculator(PowerProfile profile) {
@@ -55,59 +70,88 @@
             return;
         }
 
-        final PowerAndDuration total = new PowerAndDuration();
+        BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
+        final PowerAndDuration powerAndDuration = new PowerAndDuration();
 
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
-            calculateApp(app, total, query);
+            if (keys == UNINITIALIZED_KEYS) {
+                if (query.isProcessStateDataNeeded()) {
+                    keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
+                    powerAndDuration.keys = keys;
+                    powerAndDuration.powerPerKeyMah = new double[keys.length];
+                } else {
+                    keys = null;
+                }
+            }
+            calculateApp(app, powerAndDuration, query);
         }
 
         final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(measuredChargeUC, query);
         final ControllerActivityCounter activityCounter =
                 batteryStats.getBluetoothControllerActivity();
-        final long systemDurationMs = calculateDuration(activityCounter);
-        final double systemPowerMah = calculatePowerMah(powerModel, measuredChargeUC,
-                activityCounter, query.shouldForceUsePowerProfileModel());
+        calculatePowerAndDuration(null, powerModel, measuredChargeUC,
+                activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
 
         // Subtract what the apps used, but clamp to 0.
-        final long systemComponentDurationMs = Math.max(0, systemDurationMs - total.durationMs);
+        final long systemComponentDurationMs = Math.max(0,
+                powerAndDuration.durationMs - powerAndDuration.totalDurationMs);
         if (DEBUG) {
             Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
-                    + " power=" + formatCharge(systemPowerMah));
+                    + " power=" + formatCharge(powerAndDuration.powerMah));
         }
 
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs)
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                        powerAndDuration.durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
-                        Math.max(systemPowerMah, total.powerMah), powerModel);
+                        Math.max(powerAndDuration.powerMah, powerAndDuration.totalPowerMah),
+                        powerModel);
 
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah,
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                        powerAndDuration.totalDurationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                        powerAndDuration.totalPowerMah,
                         powerModel);
     }
 
-    private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+    private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration,
             BatteryUsageStatsQuery query) {
         final long measuredChargeUC =
                 app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(measuredChargeUC, query);
         final ControllerActivityCounter activityCounter =
                 app.getBatteryStatsUid().getBluetoothControllerActivity();
-        final long durationMs = calculateDuration(activityCounter);
-        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
-                query.shouldForceUsePowerProfileModel());
+        calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, measuredChargeUC,
+                activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
 
-        app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerMah, powerModel);
+        app.setUsageDurationMillis(
+                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.durationMs)
+                .setConsumedPower(
+                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah,
+                        powerModel);
 
-        total.durationMs += durationMs;
-        total.powerMah += powerMah;
+        powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
+        powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
+
+        if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) {
+            for (int j = 0; j < powerAndDuration.keys.length; j++) {
+                BatteryConsumer.Key key = powerAndDuration.keys[j];
+                final int processState = key.processState;
+                if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                    // Already populated with the powerAndDuration across all process states
+                    continue;
+                }
+
+                app.setConsumedPower(key, powerAndDuration.powerPerKeyMah[j], powerModel);
+            }
+        }
     }
 
     @Override
@@ -117,12 +161,12 @@
             return;
         }
 
-        PowerAndDuration total = new PowerAndDuration();
+        PowerAndDuration powerAndDuration = new PowerAndDuration();
 
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatterySipper app = sippers.get(i);
             if (app.drainType == BatterySipper.DrainType.APP) {
-                calculateApp(app, app.uidObj, statsType, total);
+                calculateApp(app, app.uidObj, statsType, powerAndDuration);
             }
         }
 
@@ -131,13 +175,14 @@
         final int powerModel = getPowerModel(measuredChargeUC);
         final ControllerActivityCounter activityCounter =
                 batteryStats.getBluetoothControllerActivity();
-        final long systemDurationMs = calculateDuration(activityCounter);
-        final double systemPowerMah =
-                calculatePowerMah(powerModel, measuredChargeUC, activityCounter, false);
+        calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false,
+                powerAndDuration);
 
         // Subtract what the apps used, but clamp to 0.
-        final double powerMah = Math.max(0, systemPowerMah - total.powerMah);
-        final long durationMs = Math.max(0, systemDurationMs - total.durationMs);
+        final double powerMah = Math.max(0,
+                powerAndDuration.powerMah - powerAndDuration.totalPowerMah);
+        final long durationMs = Math.max(0,
+                powerAndDuration.durationMs - powerAndDuration.totalDurationMs);
         if (DEBUG && powerMah != 0) {
             Log.d(TAG, "Bluetooth active: time=" + (durationMs)
                     + " power=" + formatCharge(powerMah));
@@ -160,65 +205,102 @@
     }
 
     private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
-            PowerAndDuration total) {
-
+            PowerAndDuration powerAndDuration) {
         final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(measuredChargeUC);
         final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity();
-        final long durationMs = calculateDuration(activityCounter);
-        final double powerMah = calculatePowerMah(powerModel, measuredChargeUC, activityCounter,
-                false);
+        calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter,
+                false, powerAndDuration);
 
-        app.bluetoothRunningTimeMs = durationMs;
-        app.bluetoothPowerMah = powerMah;
+        app.bluetoothRunningTimeMs = powerAndDuration.durationMs;
+        app.bluetoothPowerMah = powerAndDuration.powerMah;
         app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
         app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
 
-        total.durationMs += durationMs;
-        total.powerMah += powerMah;
-    }
-
-    private long calculateDuration(ControllerActivityCounter counter) {
-        if (counter == null) {
-            return 0;
-        }
-
-        return counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
-                + counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
-                + counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+        powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
+        powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
     }
 
     /** Returns bluetooth power usage based on the best data available. */
-    private double calculatePowerMah(@BatteryConsumer.PowerModel int powerModel,
-            long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower) {
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            return uCtoMah(measuredChargeUC);
-        }
-
+    private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid,
+            @BatteryConsumer.PowerModel int powerModel,
+            long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower,
+            PowerAndDuration powerAndDuration) {
         if (counter == null) {
-            return 0;
+            powerAndDuration.durationMs = 0;
+            powerAndDuration.powerMah = 0;
+            if (powerAndDuration.powerPerKeyMah != null) {
+                Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
+            }
+            return;
         }
 
-        if (!ignoreReportedPower) {
-            final double powerMah =
-                    counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
-                            / (double) (1000 * 60 * 60);
-            if (powerMah != 0) {
-                return powerMah;
+        final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter();
+        final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter();
+        final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0];
+        final long idleTimeMs = idleTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+        final long rxTimeMs = rxTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+        final long txTimeMs = txTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+
+        powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs;
+
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            powerAndDuration.powerMah = uCtoMah(measuredChargeUC);
+            if (uid != null && powerAndDuration.keys != null) {
+                for (int i = 0; i < powerAndDuration.keys.length; i++) {
+                    BatteryConsumer.Key key = powerAndDuration.keys[i];
+                    final int processState = key.processState;
+                    if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                        // Already populated with the powerAndDuration across all process states
+                        continue;
+                    }
+
+                    powerAndDuration.powerPerKeyMah[i] =
+                            uCtoMah(uid.getBluetoothMeasuredBatteryConsumptionUC(processState));
+                }
+            }
+        } else {
+            if (!ignoreReportedPower) {
+                final double powerMah =
+                        counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
+                                / (double) (1000 * 60 * 60);
+                if (powerMah != 0) {
+                    powerAndDuration.powerMah = powerMah;
+                    if (powerAndDuration.powerPerKeyMah != null) {
+                        // Leave this use case unsupported: used energy is reported
+                        // via BluetoothActivityEnergyInfo rather than PowerStats HAL.
+                        Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
+                    }
+                    return;
+                }
+            }
+
+            if (mHasBluetoothPowerController) {
+                powerAndDuration.powerMah = calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
+
+                if (powerAndDuration.keys != null) {
+                    for (int i = 0; i < powerAndDuration.keys.length; i++) {
+                        BatteryConsumer.Key key = powerAndDuration.keys[i];
+                        final int processState = key.processState;
+                        if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                            // Already populated with the powerAndDuration across all process states
+                            continue;
+                        }
+
+                        powerAndDuration.powerPerKeyMah[i] =
+                                calculatePowerMah(
+                                        rxTimeCounter.getCountForProcessState(processState),
+                                        txTimeCounter.getCountForProcessState(processState),
+                                        idleTimeCounter.getCountForProcessState(processState));
+                    }
+                }
+            } else {
+                powerAndDuration.powerMah = 0;
+                if (powerAndDuration.powerPerKeyMah != null) {
+                    Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
+                }
             }
         }
-
-        if (!mHasBluetoothPowerController) {
-            return 0;
-        }
-
-        final long idleTimeMs =
-                counter.getIdleTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
-        final long rxTimeMs =
-                counter.getRxTimeCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
-        final long txTimeMs =
-                counter.getTxTimeCounters()[0].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
-        return calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
     }
 
     /** Returns estimated bluetooth power usage based on usage times. */
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index 7766b77..fd1d86b 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -12,4 +12,5 @@
 per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS
 per-file *Kernel* = file:/BATTERY_STATS_OWNERS
 per-file *MultiState* = file:/BATTERY_STATS_OWNERS
+per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS
 
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 4d19b35..54e65e0 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -17,24 +17,31 @@
 package com.android.internal.os;
 
 
+import android.annotation.LongDef;
 import android.annotation.StringDef;
+import android.annotation.XmlRes;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.power.ModemPowerProfile;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 /**
@@ -259,6 +266,34 @@
     public @interface PowerGroup {}
 
     /**
+     * Constants for generating a 64bit power constant key.
+     *
+     * The bitfields of a key describes what its corresponding power constant represents:
+     * [63:40] - RESERVED
+     * [39:32] - {@link Subsystem} (max count = 16).
+     * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}.
+     *
+     */
+    private static final long SUBSYSTEM_MASK = 0xF_0000_0000L;
+    /**
+     * Power constant not associated with a subsystem.
+     */
+    public static final long SUBSYSTEM_NONE = 0x0_0000_0000L;
+    /**
+     * Modem power constant.
+     */
+    public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L;
+
+    @LongDef(prefix = { "SUBSYSTEM_" }, value = {
+            SUBSYSTEM_NONE,
+            SUBSYSTEM_MODEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Subsystem {}
+
+    private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF;
+
+    /**
      * A map from Power Use Item to its power consumption.
      */
     static final HashMap<String, Double> sPowerItemMap = new HashMap<>();
@@ -268,12 +303,16 @@
      */
     static final HashMap<String, Double[]> sPowerArrayMap = new HashMap<>();
 
+    static final ModemPowerProfile sModemPowerProfile = new ModemPowerProfile();
+
     private static final String TAG_DEVICE = "device";
     private static final String TAG_ITEM = "item";
     private static final String TAG_ARRAY = "array";
     private static final String TAG_ARRAYITEM = "value";
     private static final String ATTR_NAME = "name";
 
+    private static final String TAG_MODEM = "modem";
+
     private static final Object sLock = new Object();
 
     @VisibleForTesting
@@ -289,19 +328,40 @@
     public PowerProfile(Context context, boolean forTest) {
         // Read the XML file for the given profile (normally only one per device)
         synchronized (sLock) {
-            if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
-                readPowerValuesFromXml(context, forTest);
-            }
-            initCpuClusters();
-            initDisplays();
+            final int xmlId = forTest ? com.android.internal.R.xml.power_profile_test
+                    : com.android.internal.R.xml.power_profile;
+            initLocked(context, xmlId);
         }
     }
 
-    private void readPowerValuesFromXml(Context context, boolean forTest) {
-        final int id = forTest ? com.android.internal.R.xml.power_profile_test :
-                com.android.internal.R.xml.power_profile;
+    /**
+     * Reinitialize the PowerProfile with the provided XML.
+     * WARNING: use only for testing!
+     */
+    @VisibleForTesting
+    public void forceInitForTesting(Context context, @XmlRes int xmlId) {
+        synchronized (sLock) {
+            sPowerItemMap.clear();
+            sPowerArrayMap.clear();
+            sModemPowerProfile.clear();
+            initLocked(context, xmlId);
+        }
+
+    }
+
+    @GuardedBy("sLock")
+    private void initLocked(Context context, @XmlRes int xmlId) {
+        if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) {
+            readPowerValuesFromXml(context, xmlId);
+        }
+        initCpuClusters();
+        initDisplays();
+        initModem();
+    }
+
+    private void readPowerValuesFromXml(Context context, @XmlRes int xmlId) {
         final Resources resources = context.getResources();
-        XmlResourceParser parser = resources.getXml(id);
+        XmlResourceParser parser = resources.getXml(xmlId);
         boolean parsingArray = false;
         ArrayList<Double> array = new ArrayList<>();
         String arrayName = null;
@@ -340,6 +400,8 @@
                             array.add(value);
                         }
                     }
+                } else if (element.equals(TAG_MODEM)) {
+                    sModemPowerProfile.parseFromXml(parser);
                 }
             }
             if (parsingArray) {
@@ -515,6 +577,39 @@
         return mNumDisplays;
     }
 
+    private void initModem() {
+        handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+                POWER_MODEM_CONTROLLER_SLEEP, 0);
+        handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+                POWER_MODEM_CONTROLLER_IDLE, 0);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX,
+                POWER_MODEM_CONTROLLER_RX, 0);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0, POWER_MODEM_CONTROLLER_TX, 0);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1, POWER_MODEM_CONTROLLER_TX, 1);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2, POWER_MODEM_CONTROLLER_TX, 2);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3, POWER_MODEM_CONTROLLER_TX, 3);
+        handleDeprecatedModemConstant(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4, POWER_MODEM_CONTROLLER_TX, 4);
+    }
+
+    private void handleDeprecatedModemConstant(int key, String deprecatedKey, int level) {
+        final double drain = sModemPowerProfile.getAverageBatteryDrainMa(key);
+        if (!Double.isNaN(drain)) return; // Value already set, don't overwrite it.
+
+        final double deprecatedDrain = getAveragePower(deprecatedKey, level);
+        sModemPowerProfile.setPowerConstant(key, Double.toString(deprecatedDrain));
+    }
+
     /**
      * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a
      * default value if the subsystem has no recorded value.
@@ -560,6 +655,43 @@
     }
 
     /**
+     * Returns the average current in mA consumed by a subsystem's specified operation, or the given
+     * default value if the subsystem has no recorded value.
+     *
+     * @param key that describes a subsystem's battery draining operation
+     *            The key is built from multiple constant, see {@link Subsystem} and
+     *            {@link ModemPowerProfile}.
+     * @param defaultValue the value to return if the subsystem has no recorded value.
+     * @return the average current in milliAmps.
+     */
+    public double getAverageBatteryDrainOrDefaultMa(long key, double defaultValue) {
+        final long subsystemType = key & SUBSYSTEM_MASK;
+        final int subsystemFields = (int) (key & SUBSYSTEM_FIELDS_MASK);
+
+        final double value;
+        if (subsystemType == SUBSYSTEM_MODEM) {
+            value = sModemPowerProfile.getAverageBatteryDrainMa(subsystemFields);
+        } else {
+            value = Double.NaN;
+        }
+
+        if (Double.isNaN(value)) return defaultValue;
+        return value;
+    }
+
+    /**
+     * Returns the average current in mA consumed by a subsystem's specified operation.
+     *
+     * @param key that describes a subsystem's battery draining operation
+     *            The key is built from multiple constant, see {@link Subsystem} and
+     *            {@link ModemPowerProfile}.
+     * @return the average current in milliAmps.
+     */
+    public double getAverageBatteryDrainMa(long key) {
+        return getAverageBatteryDrainOrDefaultMa(key, 0);
+    }
+
+    /**
      * Returns the average current in mA consumed by the subsystem for the given level.
      *
      * @param type  the subsystem type
@@ -784,6 +916,25 @@
                 PowerProfileProto.BATTERY_CAPACITY);
     }
 
+    /**
+     * Dump the PowerProfile values.
+     */
+    public void dump(PrintWriter pw) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        sPowerItemMap.forEach((key, value) -> {
+            ipw.print(key, value);
+            ipw.println();
+        });
+        sPowerArrayMap.forEach((key, value) -> {
+            ipw.print(key, Arrays.toString(value));
+            ipw.println();
+        });
+        ipw.println("Modem values:");
+        ipw.increaseIndent();
+        sModemPowerProfile.dump(ipw);
+        ipw.decreaseIndent();
+    }
+
     // Writes items in sPowerItemMap to proto if exists.
     private void writePowerConstantToProto(ProtoOutputStream proto, String key, long fieldId) {
         if (sPowerItemMap.containsKey(key)) {
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/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index ba7a0ef..d7eeb7b 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -87,6 +87,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.OnBackInvokedDispatcher;
 import android.view.PendingInsetsController;
 import android.view.ThreadedRenderer;
 import android.view.View;
@@ -108,6 +109,7 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import android.widget.PopupWindow;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.internal.R;
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
@@ -294,6 +296,7 @@
         return true;
     };
     private Consumer<Boolean> mCrossWindowBlurEnabledListener;
+    private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
 
     DecorView(Context context, int featureId, PhoneWindow window,
             WindowManager.LayoutParams params) {
@@ -322,6 +325,7 @@
         initResizingPaints();
 
         mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
+        mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher();
     }
 
     void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -1869,6 +1873,7 @@
         }
 
         mPendingInsetsController.detach();
+        mOnBackInvokedDispatcher.detachFromWindow();
     }
 
     @Override
@@ -1913,6 +1918,11 @@
         return mPendingInsetsController;
     }
 
+    @Override
+    public WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher() {
+        return mOnBackInvokedDispatcher;
+    }
+
     private ActionMode createActionMode(
             int type, ActionMode.Callback2 callback, View originatingView) {
         switch (type) {
@@ -2367,6 +2377,7 @@
                 }
             }
         }
+        mOnBackInvokedDispatcher.clear();
     }
 
     @Override
@@ -2648,6 +2659,15 @@
         }
     }
 
+    /**
+     * Returns the {@link OnBackInvokedDispatcher} on the decor view.
+     */
+    @Override
+    @Nullable
+    public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
+        return mOnBackInvokedDispatcher;
+    }
+
     @Override
     public String toString() {
         return "DecorView@" + Integer.toHexString(this.hashCode()) + "["
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
new file mode 100644
index 0000000..afea69a
--- /dev/null
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -0,0 +1,475 @@
+/*
+ * 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.power;
+
+import android.annotation.IntDef;
+import android.content.res.XmlResourceParser;
+import android.telephony.ModemActivityInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseDoubleArray;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * ModemPowerProfile for handling the modem element in the power_profile.xml
+ */
+public class ModemPowerProfile {
+    private static final String TAG = "ModemPowerProfile";
+
+    private static final String TAG_SLEEP = "sleep";
+    private static final String TAG_IDLE = "idle";
+    private static final String TAG_ACTIVE = "active";
+    private static final String TAG_RECEIVE = "receive";
+    private static final String TAG_TRANSMIT = "transmit";
+    private static final String ATTR_RAT = "rat";
+    private static final String ATTR_NR_FREQUENCY = "nrFrequency";
+    private static final String ATTR_LEVEL = "level";
+
+    /**
+     * A flattened list of the modem power constant extracted from the given XML parser.
+     *
+     * The bitfields of a key describes what its corresponding power constant represents:
+     * [31:28] - {@link ModemDrainType} (max count = 16).
+     * [27:24] - {@link ModemTxLevel} (only for {@link MODEM_DRAIN_TYPE_TX}) (max count = 16).
+     * [23:20] - {@link ModemRatType} (max count = 16).
+     * [19:16] - {@link ModemNrFrequencyRange} (only for {@link MODEM_RAT_TYPE_NR})
+     * (max count = 16).
+     * [15:0] - RESERVED
+     */
+    private final SparseDoubleArray mPowerConstants = new SparseDoubleArray();
+
+    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 = 0x0000_0000;
+
+    /**
+     * Corresponds to the overall modem battery drain while idle.
+     */
+    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 = 0x2000_0000;
+
+    /**
+     * Corresponds to the modem battery drain while receiving data.
+     * {@link ModemTxLevel} must be specified with this drain type.
+     * 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 = 0x3000_0000;
+
+    @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = {
+            MODEM_DRAIN_TYPE_SLEEP,
+            MODEM_DRAIN_TYPE_IDLE,
+            MODEM_DRAIN_TYPE_RX,
+            MODEM_DRAIN_TYPE_TX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModemDrainType {
+    }
+
+
+    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 = 0x0000_0000;
+
+    /**
+     * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}.
+     */
+
+    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 = 0x0200_0000;
+
+    /**
+     * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}.
+     */
+
+    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 = 0x0400_0000;
+
+    private static final int MODEM_TX_LEVEL_COUNT = 5;
+
+    @IntDef(prefix = {"MODEM_TX_LEVEL_"}, value = {
+            MODEM_TX_LEVEL_0,
+            MODEM_TX_LEVEL_1,
+            MODEM_TX_LEVEL_2,
+            MODEM_TX_LEVEL_3,
+            MODEM_TX_LEVEL_4,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    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 = 0x0000_0000;
+
+    /**
+     * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT.
+     */
+    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 = 0x0020_0000;
+
+    @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = {
+            MODEM_RAT_TYPE_DEFAULT,
+            MODEM_RAT_TYPE_LTE,
+            MODEM_RAT_TYPE_NR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModemRatType {
+    }
+
+    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 = 0x0000_0000;
+
+    /**
+     * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}.
+     */
+    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 = 0x0002_0000;
+
+    /**
+     * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}.
+     */
+    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 = 0x0004_0000;
+
+    @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = {
+            MODEM_RAT_TYPE_DEFAULT,
+            MODEM_NR_FREQUENCY_RANGE_LOW,
+            MODEM_NR_FREQUENCY_RANGE_MID,
+            MODEM_NR_FREQUENCY_RANGE_HIGH,
+            MODEM_NR_FREQUENCY_RANGE_MMWAVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ModemNrFrequencyRange {
+    }
+    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() {
+    }
+
+    /**
+     * Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml
+     */
+    public void parseFromXml(XmlResourceParser parser) throws IOException,
+            XmlPullParserException {
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            final String name = parser.getName();
+            switch (name) {
+                case TAG_SLEEP:
+                    if (parser.next() != XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    final String sleepDrain = parser.getText();
+                    setPowerConstant(MODEM_DRAIN_TYPE_SLEEP, sleepDrain);
+                    break;
+                case TAG_IDLE:
+                    if (parser.next() != XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    final String idleDrain = parser.getText();
+                    setPowerConstant(MODEM_DRAIN_TYPE_IDLE, idleDrain);
+                    break;
+                case TAG_ACTIVE:
+                    parseActivePowerConstantsFromXml(parser);
+                    break;
+                default:
+                    Slog.e(TAG, "Unexpected element parsed: " + name);
+            }
+        }
+    }
+
+    /** Parse the <active /> XML element */
+    private void parseActivePowerConstantsFromXml(XmlResourceParser parser)
+            throws IOException, XmlPullParserException {
+        // Parse attributes to get the type of active modem usage the power constants are for.
+        final int ratType;
+        final int nrfType;
+        try {
+            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_NAMES);
+            } else {
+                nrfType = 0;
+            }
+        } catch (IllegalArgumentException iae) {
+            Slog.e(TAG, "Failed parse to active modem power constants", iae);
+            return;
+        }
+
+        // Parse and populate the active modem use power constants.
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            final String name = parser.getName();
+            switch (name) {
+                case TAG_RECEIVE:
+                    if (parser.next() != XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    final String rxDrain = parser.getText();
+                    final int rxKey = MODEM_DRAIN_TYPE_RX | ratType | nrfType;
+                    setPowerConstant(rxKey, rxDrain);
+                    break;
+                case TAG_TRANSMIT:
+                    final int level = XmlUtils.readIntAttribute(parser, ATTR_LEVEL, -1);
+                    if (parser.next() != XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    final String txDrain = parser.getText();
+                    if (level < 0 || level >= MODEM_TX_LEVEL_COUNT) {
+                        Slog.e(TAG,
+                                "Unexpected tx level: " + level + ". Must be between 0 and " + (
+                                        MODEM_TX_LEVEL_COUNT - 1));
+                        continue;
+                    }
+                    final int modemTxLevel = MODEM_TX_LEVEL_MAP[level];
+                    final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType;
+                    setPowerConstant(txKey, txDrain);
+                    break;
+                default:
+                    Slog.e(TAG, "Unexpected element parsed: " + name);
+            }
+        }
+    }
+
+    private static int getTypeFromAttribute(XmlResourceParser parser, String attr,
+            SparseArray<String> names) {
+        final String value = XmlUtils.readStringAttribute(parser, attr);
+        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(stringNames));
+        }
+        return names.keyAt(index);
+    }
+
+    /**
+     * Set the average battery drain in milli-amps of the modem for a given drain type.
+     *
+     * @param key   a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel},
+     *              {@link ModemRatType}, and {@link ModemNrFrequencyRange}.key
+     * @param value the battery dram in milli-amps for the given key.
+     */
+    public void setPowerConstant(int key, String value) {
+        try {
+            mPowerConstants.put(key, Double.valueOf(value));
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to set power constant 0x" + Integer.toHexString(
+                    key) + "(" + keyToString(key) + ") to " + value, e);
+        }
+    }
+
+    /**
+     * Returns the average battery drain in milli-amps of the modem for a given drain type.
+     * Returns {@link Double.NaN} if a suitable value is not found for the given key.
+     *
+     * @param key a key built from the union of {@link ModemDrainType}, {@link ModemTxLevel},
+     *            {@link ModemRatType}, and {@link ModemNrFrequencyRange}.
+     */
+    public double getAverageBatteryDrainMa(int key) {
+        int bestKey = key;
+        double value;
+        value = mPowerConstants.get(bestKey, Double.NaN);
+        if (!Double.isNaN(value)) return value;
+        // The power constant for given key was not explicitly set. Try to fallback to possible
+        // defaults.
+
+        if ((bestKey & MODEM_NR_FREQUENCY_RANGE_MASK) != MODEM_NR_FREQUENCY_RANGE_DEFAULT) {
+            // Fallback to NR Frequency default value
+            bestKey &= ~MODEM_NR_FREQUENCY_RANGE_MASK;
+            bestKey |= MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+            value = mPowerConstants.get(bestKey, Double.NaN);
+            if (!Double.isNaN(value)) return value;
+        }
+
+        if ((bestKey & MODEM_RAT_TYPE_MASK) != MODEM_RAT_TYPE_DEFAULT) {
+            // Fallback to RAT default value
+            bestKey &= ~MODEM_RAT_TYPE_MASK;
+            bestKey |= MODEM_RAT_TYPE_DEFAULT;
+            value = mPowerConstants.get(bestKey, Double.NaN);
+            if (!Double.isNaN(value)) return value;
+        }
+
+        Slog.w(TAG,
+                "getAverageBatteryDrainMaH called with unexpected key: 0x" + Integer.toHexString(
+                        key) + ", " + keyToString(key));
+        return Double.NaN;
+    }
+
+    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);
+        sb.append(",");
+
+        if (drainType == MODEM_DRAIN_TYPE_TX) {
+            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);
+
+        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);
+        }
+        return sb.toString();
+    }
+    private static void appendFieldToString(StringBuilder sb, String fieldName,
+            SparseArray<String> names, int key) {
+        sb.append(fieldName);
+        sb.append(":");
+        final String name = names.get(key, null);
+        if (name == null) {
+            sb.append("UNKNOWN(0x");
+            sb.append(Integer.toHexString(key));
+            sb.append(")");
+        } else {
+            sb.append(name);
+        }
+    }
+
+    /**
+     * Clear this ModemPowerProfile power constants.
+     */
+    public void clear() {
+        mPowerConstants.clear();
+    }
+
+
+    /**
+     * Dump this ModemPowerProfile power constants.
+     */
+    public void dump(PrintWriter pw) {
+        final int size = mPowerConstants.size();
+        for (int i = 0; i < size; i++) {
+            pw.print(keyToString(mPowerConstants.keyAt(i)));
+            pw.print("=");
+            pw.println(mPowerConstants.valueAt(i));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 5ac4936..def598c 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -85,6 +85,8 @@
     WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM),
     WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
+    WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+            "CoreBackPreview"),
     TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 1d62623..4f80afa 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -81,9 +81,9 @@
     }
 
     public void readFromParcel(Parcel in) {
-        this.icon = (Icon) in.readParcelable(null);
+        this.icon = (Icon) in.readParcelable(null, android.graphics.drawable.Icon.class);
         this.pkg = in.readString();
-        this.user = (UserHandle) in.readParcelable(null);
+        this.user = (UserHandle) in.readParcelable(null, android.os.UserHandle.class);
         this.iconLevel = in.readInt();
         this.visible = in.readInt() != 0;
         this.number = in.readInt();
diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java
index 3260136..b32a6b0 100644
--- a/core/java/com/android/internal/usb/DumpUtils.java
+++ b/core/java/com/android/internal/usb/DumpUtils.java
@@ -244,7 +244,12 @@
         writeContaminantPresenceStatus(dump, "contaminant_presence_status",
                 UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS,
                 status.getContaminantDetectionStatus());
-
+        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/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f46223a..d3c3917 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -71,11 +71,11 @@
 
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
-                mInsets = in.readParcelable(Insets.class.getClassLoader());
+                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
+                mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
                 mTaskId = in.readInt();
                 mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader());
+                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
             }
         }
 
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 402fa64..6a626ee 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -53,8 +53,6 @@
 
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
 
-    void revokeSession(IInputMethodSession session);
-
     void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver);
 
     void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver);
diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
index efd5fb2..4b89bf508 100644
--- a/core/java/com/android/internal/view/RootViewSurfaceTaker.java
+++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
@@ -15,11 +15,12 @@
  */
 package com.android.internal.view;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.view.InputQueue;
 import android.view.PendingInsetsController;
 import android.view.SurfaceHolder;
-import android.view.WindowInsetsController;
+import android.window.WindowOnBackInvokedDispatcher;
 
 /** hahahah */
 public interface RootViewSurfaceTaker {
@@ -30,4 +31,6 @@
     InputQueue.Callback willYouTakeTheInputQueue();
     void onRootViewScrollYChanged(int scrollY);
     @Nullable PendingInsetsController providePendingInsetsController();
+    /** @hide */
+    @NonNull WindowOnBackInvokedDispatcher provideWindowOnBackInvokedDispatcher();
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a3ac472..430d84e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -124,7 +124,6 @@
                 "android_view_PointerIcon.cpp",
                 "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
-                "android_view_SurfaceControlFpsListener.cpp",
                 "android_view_SurfaceControlHdrLayerInfoListener.cpp",
                 "android_graphics_BLASTBufferQueue.cpp",
                 "android_view_SurfaceSession.cpp",
@@ -255,7 +254,6 @@
                 "libandroidicu",
                 "libbattery",
                 "libbpf_android",
-                "libnetdbpf",
                 "libnetdutils",
                 "libmemtrack",
                 "libandroidfw",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 21ec64b..f4296be 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -123,7 +123,6 @@
 extern int register_android_view_InputWindowHandle(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_SurfaceControl(JNIEnv* env);
-extern int register_android_view_SurfaceControlFpsListener(JNIEnv* env);
 extern int register_android_view_SurfaceControlHdrLayerInfoListener(JNIEnv* env);
 extern int register_android_view_SurfaceSession(JNIEnv* env);
 extern int register_android_view_CompositionSamplingListener(JNIEnv* env);
@@ -1546,7 +1545,6 @@
         REG_JNI(register_android_view_InputWindowHandle),
         REG_JNI(register_android_view_Surface),
         REG_JNI(register_android_view_SurfaceControl),
-        REG_JNI(register_android_view_SurfaceControlFpsListener),
         REG_JNI(register_android_view_SurfaceControlHdrLayerInfoListener),
         REG_JNI(register_android_view_SurfaceSession),
         REG_JNI(register_android_view_CompositionSamplingListener),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 4357729..9a460f5 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -76,6 +76,9 @@
 per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google.com
 # Although marked "view" this is mostly graphics stuff
 per-file android_view_* = file:/graphics/java/android/graphics/OWNERS
+# File used for Android Studio layoutlib
+per-file LayoutlibLoader.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file LayoutlibLoader.cpp = diegoperez@google.com, jgaillard@google.com
 
 # Verity
 per-file com_android_internal_security_Verity* = ebiggers@google.com, victorhsieh@google.com
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index 423ef7c..d7fc1cc 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -57,7 +57,7 @@
     jmethodID setUsage;
     jmethodID setSystemUsage;
     jmethodID setInternalCapturePreset;
-    jmethodID setContentType;
+    jmethodID setInternalContentType;
     jmethodID replaceFlags;
     jmethodID addTag;
 } gAudioAttributesBuilderMethods;
@@ -127,7 +127,7 @@
                           gAudioAttributesBuilderMethods.setInternalCapturePreset,
                           attributes.source);
     env->CallObjectMethod(jAttributeBuilder.get(),
-                          gAudioAttributesBuilderMethods.setContentType,
+                          gAudioAttributesBuilderMethods.setInternalContentType,
                           attributes.content_type);
     env->CallObjectMethod(jAttributeBuilder.get(),
                           gAudioAttributesBuilderMethods.replaceFlags,
@@ -202,9 +202,9 @@
     gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "setInternalCapturePreset",
                 "(I)Landroid/media/AudioAttributes$Builder;");
-    gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie(
-                env, audioAttributesBuilderClass, "setContentType",
-                "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.setInternalContentType =
+            GetMethodIDOrDie(env, audioAttributesBuilderClass, "setInternalContentType",
+                             "(I)Landroid/media/AudioAttributes$Builder;");
     gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "replaceFlags",
                 "(I)Landroid/media/AudioAttributes$Builder;");
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 3651dbd..571a8e2 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -127,6 +127,7 @@
     addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Oriya
     addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Punjabi
     addHyphenator("pt", 2, 3);  // Portuguese
+    addHyphenator("ru", 2, 2);  // Russian
     addHyphenator("sk", 2, 2);  // Slovak
     addHyphenator("sl", 2, 2);  // Slovenian
     addHyphenator("sq", 2, 2);  // Albanian
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index dd5af04..d5470cc 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -91,6 +91,7 @@
     jfieldID density;
     jfieldID secure;
     jfieldID deviceProductInfo;
+    jfieldID installOrientation;
 } gStaticDisplayInfoClassInfo;
 
 static struct {
@@ -1210,6 +1211,8 @@
     env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure);
     env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo,
                         convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo));
+    env->SetIntField(object, gStaticDisplayInfoClassInfo.installOrientation,
+                     static_cast<uint32_t>(info.installOrientation));
     return object;
 }
 
@@ -2152,6 +2155,8 @@
     gStaticDisplayInfoClassInfo.deviceProductInfo =
             GetFieldIDOrDie(env, infoClazz, "deviceProductInfo",
                             "Landroid/hardware/display/DeviceProductInfo;");
+    gStaticDisplayInfoClassInfo.installOrientation =
+            GetFieldIDOrDie(env, infoClazz, "installOrientation", "I");
 
     jclass dynamicInfoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DynamicDisplayInfo");
     gDynamicDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, dynamicInfoClazz);
diff --git a/core/jni/android_view_SurfaceControlFpsListener.cpp b/core/jni/android_view_SurfaceControlFpsListener.cpp
deleted file mode 100644
index 0b15acd..0000000
--- a/core/jni/android_view_SurfaceControlFpsListener.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#define LOG_TAG "SurfaceControlFpsListener"
-
-#include <android/gui/BnFpsListener.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/Log.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <nativehelper/JNIHelp.h>
-#include <utils/Log.h>
-#include <utils/RefBase.h>
-
-#include "android_util_Binder.h"
-#include "core_jni_helpers.h"
-
-namespace android {
-
-namespace {
-
-struct {
-    jclass mClass;
-    jmethodID mDispatchOnFpsReported;
-} gListenerClassInfo;
-
-struct SurfaceControlFpsListener : public gui::BnFpsListener {
-    SurfaceControlFpsListener(JNIEnv* env, jobject listener)
-          : mListener(env->NewWeakGlobalRef(listener)) {}
-
-    binder::Status onFpsReported(float fps) override {
-        JNIEnv* env = AndroidRuntime::getJNIEnv();
-        LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onFpsReported.");
-
-        jobject listener = env->NewGlobalRef(mListener);
-        if (listener == NULL) {
-            // Weak reference went out of scope
-            return binder::Status::ok();
-        }
-        env->CallStaticVoidMethod(gListenerClassInfo.mClass,
-                                  gListenerClassInfo.mDispatchOnFpsReported, listener,
-                                  static_cast<jfloat>(fps));
-        env->DeleteGlobalRef(listener);
-
-        if (env->ExceptionCheck()) {
-            ALOGE("SurfaceControlFpsListener.onFpsReported() failed.");
-            LOGE_EX(env);
-            env->ExceptionClear();
-        }
-        return binder::Status::ok();
-    }
-
-protected:
-    virtual ~SurfaceControlFpsListener() {
-        JNIEnv* env = AndroidRuntime::getJNIEnv();
-        env->DeleteWeakGlobalRef(mListener);
-    }
-
-private:
-    jweak mListener;
-};
-
-jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) {
-    SurfaceControlFpsListener* listener = new SurfaceControlFpsListener(env, obj);
-    listener->incStrong((void*)nativeCreate);
-    return reinterpret_cast<jlong>(listener);
-}
-
-void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
-    SurfaceControlFpsListener* listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr);
-    listener->decStrong((void*)nativeCreate);
-}
-
-void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr, jint taskId) {
-    sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr);
-    if (SurfaceComposerClient::addFpsListener(taskId, listener) != OK) {
-        constexpr auto error_msg = "Couldn't addFpsListener";
-        ALOGE(error_msg);
-        jniThrowRuntimeException(env, error_msg);
-    }
-}
-
-void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) {
-    sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr);
-
-    if (SurfaceComposerClient::removeFpsListener(listener) != OK) {
-        constexpr auto error_msg = "Couldn't removeFpsListener";
-        ALOGE(error_msg);
-        jniThrowRuntimeException(env, error_msg);
-    }
-}
-
-const JNINativeMethod gMethods[] = {
-        /* name, signature, funcPtr */
-        {"nativeCreate", "(Landroid/view/SurfaceControlFpsListener;)J", (void*)nativeCreate},
-        {"nativeDestroy", "(J)V", (void*)nativeDestroy},
-        {"nativeRegister", "(JI)V", (void*)nativeRegister},
-        {"nativeUnregister", "(J)V", (void*)nativeUnregister}};
-
-} // namespace
-
-int register_android_view_SurfaceControlFpsListener(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "android/view/SurfaceControlFpsListener", gMethods,
-                                       NELEM(gMethods));
-    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
-
-    jclass clazz = env->FindClass("android/view/SurfaceControlFpsListener");
-    gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz);
-    gListenerClassInfo.mDispatchOnFpsReported =
-            env->GetStaticMethodID(clazz, "dispatchOnFpsReported",
-                                   "(Landroid/view/SurfaceControlFpsListener;F)V");
-    return 0;
-}
-
-} // namespace android
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 78650ed..a4463e4 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -18,7 +18,8 @@
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
 
 # Biometrics
-kchyn@google.com
+jaggies@google.com
+jbolinger@google.com
 
 # Launcher
 hyunyoungs@google.com
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/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index fbe2170..2f2158d 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -97,7 +97,7 @@
     optional int32 status = 6;
 }
 
-// Next id: 24
+// Next id: 25
 message VibratorManagerServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     repeated int32 vibrator_ids = 1;
@@ -106,6 +106,7 @@
     optional VibrationProto current_external_vibration = 4;
     optional bool vibrator_under_external_control = 5;
     optional bool low_power_mode = 6;
+    optional bool vibrate_on = 24;
     optional int32 alarm_intensity = 18;
     optional int32 alarm_default_intensity = 19;
     optional int32 haptic_feedback_intensity = 7;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 23453876..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 {
@@ -225,7 +225,8 @@
     repeated InsetsSourceProviderProto insets_source_providers = 35;
     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 */
@@ -438,11 +439,12 @@
     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;
     optional float global_scale = 44;
+    repeated .android.graphics.RectProto keep_clear_areas = 45;
 }
 
 message IdentifierProto {
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index 97097ff..c5eaf42 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -196,9 +196,19 @@
 message UsbPortManagerProto {
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
+    enum HalVersion {
+        V_UNKNOWN = 0;
+        V1_0 = 10;
+        V1_1 = 11;
+        V1_2 = 12;
+        V1_3 = 13;
+        V2 = 20;
+    }
+
     optional bool is_simulation_active = 1;
     repeated UsbPortInfoProto usb_ports = 2;
     optional bool enable_usb_data_signaling = 3;
+    optional HalVersion hal_version = 4;
 }
 
 message UsbPortInfoProto {
@@ -254,6 +264,9 @@
     optional DataRole data_role = 4;
     repeated UsbPortStatusRoleCombinationProto role_combinations = 5;
     optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6;
+    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 ae2052b..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" />
 
@@ -4745,6 +4821,12 @@
     <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
         android:protectionLevel="signature|privileged|role" />
 
+    <!-- @SystemApi Allows an application to access the ultrasound content.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.ACCESS_ULTRASOUND"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Puts an application in the chain of trust for sound trigger
          operations. Being in the chain of trust allows an application to
          delegate an identity of a separate entity to the sound trigger system
@@ -5122,6 +5204,11 @@
     <permission android:name="android.permission.SET_WALLPAPER_COMPONENT"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows applications to set the wallpaper dim amount.
+         @hide. -->
+    <permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows applications to read dream settings and dream state.
          @hide -->
     <permission android:name="android.permission.READ_DREAM_STATE"
@@ -6032,10 +6119,15 @@
     <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
                 android:protectionLevel="signature" />
 
-    <!-- Allows managing the Game Mode
-     @hide Used internally. -->
+    <!-- @SystemApi Allows managing the Game Mode
+     @hide -->
     <permission android:name="android.permission.MANAGE_GAME_MODE"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Allows accessing the frame rate per second of a given application
+     @hide -->
+    <permission android:name="android.permission.ACCESS_FPS_COUNTER"
+                android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
          when they are performing reboot-blocking work.
@@ -6094,11 +6186,21 @@
     <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"
         android:protectionLevel="signature" />
 
-      <!-- @SystemApi Allows an application to query over global data in AppSearch.
+    <!-- @SystemApi Allows an application to query over global data in AppSearch.
            @hide -->
     <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to query over global data in AppSearch that's visible to the
+         ASSISTANT role.  -->
+    <permission android:name="android.permission.READ_ASSISTANT_APP_SEARCH_DATA"
+        android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to query over global data in AppSearch that's visible to the
+         HOME role.  -->
+    <permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA"
+        android:protectionLevel="internal|role" />
+
     <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
          @hide -->
     <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
@@ -6112,7 +6214,7 @@
     <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
                 android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to launch device manager setup screens.
+    <!-- @SystemApi Allows an application to launch device manager setup screens.
          <p>Not for use by third-party applications.
          @hide
     -->
@@ -6140,6 +6242,19 @@
     <permission android:name="android.permission.MANAGE_SAFETY_CENTER"
                 android:protectionLevel="internal|installer|role" />
 
+    <!-- @SystemApi Allows an application to access the AmbientContextEvent service.
+         @hide
+    -->
+    <permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"
+                android:protectionLevel="internal|role"/>
+
+    <!-- @SystemApi Required by a AmbientContextEventDetectionService
+         to ensure that only the service with this permission can bind to it.
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -6160,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"
@@ -6651,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/OWNERS b/core/res/OWNERS
index b18a989..4bea4d5 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -34,3 +34,7 @@
 
 # Wear
 per-file res/*-watch/* = file:/platform/frameworks/opt/wear:/OWNERS
+
+# PowerProfile
+per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS
+per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS
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 e232d85..8696f5a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3347,6 +3347,14 @@
              <p>Note that this flag will only be respected if the View's Outline returns true from
              {@link android.graphics.Outline#canClip()}. -->
         <attr name="clipToOutline" format="boolean" />
+
+        <!-- <p> Sets a preference to keep the bounds of this view clear from floating windows
+            above this view's window. This informs the system that the view is considered a vital
+            area for the user and that ideally it should not be covered. Setting this is only
+            appropriate for UI where the user would likely take action to uncover it.
+            <p>The system will try to respect this, but when not possible will ignore it.
+            See {@link android.view.View#setPreferKeepClear}. -->
+        <attr name="preferKeepClear" format="boolean" />
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -5030,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
@@ -5428,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">
@@ -9376,11 +9395,12 @@
         <attr name="canPauseRecording" format="boolean" />
     </declare-styleable>
 
-    <!-- Use <code>tv-iapp</code> as the root tag of the XML resource that describes a
-         {@link android.media.tv.interactive.TvIAppService}, which is referenced from its
-         {@link android.media.tv.interactive.TvIAppService#SERVICE_META_DATA} meta-data entry.
-         Described here are the attributes that can be included in that tag. -->
-    <declare-styleable name="TvIAppService">
+    <!-- Use <code>tv-interactive-app</code> as the root tag of the XML resource that describes a
+         {@link android.media.tv.interactive.TvInteractiveAppService}, which is referenced
+         from its
+         {@link android.media.tv.interactive.TvInteractiveAppService#SERVICE_META_DATA}
+         meta-data entry. Described here are the attributes that can be included in that tag. -->
+    <declare-styleable name="TvInteractiveAppService">
         <!-- The interactive app types that the TV interactive app service supports.
              Reference to a string array resource that describes the supported types,
              e.g. HbbTv, Ginga. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index a595433..3a2fb6e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -401,6 +401,15 @@
          and before. -->
     <attr name="sharedUserMaxSdkVersion" format="integer" />
 
+    <!-- Whether the application should inherit all AndroidKeyStore keys of its shared user
+         group in the case of leaving its shared user ID in an upgrade.  If set to false, all
+         AndroidKeyStore keys will remain in the shared user group, and the application will no
+         longer have access to those keys after the upgrade. If set to true, all AndroidKeyStore
+         keys owned by the shared user group will be transferred to the upgraded application;
+         other applications in the shared user group will no longer have access to those keys
+         after the migration. The default value is false if not explicitly set. -->
+    <attr name="inheritKeyStoreKeys" format="boolean" />
+
     <!-- Internal version code.  This is the number used to determine whether
          one version is more recent than another: it has no other meaning than
          that higher numbers are more recent.  You could use this number to
@@ -1677,6 +1686,7 @@
         <attr name="sharedUserId" />
         <attr name="sharedUserLabel" />
         <attr name="sharedUserMaxSdkVersion" />
+        <attr name="inheritKeyStoreKeys" />
         <attr name="installLocation" />
         <attr name="isolatedSplits" />
         <attr name="isFeatureSplit" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f4b7b73..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>
 
@@ -2135,14 +2113,15 @@
     <string name="config_systemAppProtectionService" translatable="false"></string>
     <!-- The name of the package that will hold the system calendar sync manager role. -->
     <string name="config_systemAutomotiveCalendarSyncManager" translatable="false"></string>
+    <!-- The name of the package that will hold the default automotive navigation role. -->
+    <string name="config_defaultAutomotiveNavigation" translatable="false"></string>
+
+    <!-- The name of the package that will handle updating the device management role. -->
+    <string name="config_deviceManagerUpdater" translatable="false"></string>
 
     <!-- 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>
 
@@ -4072,6 +4051,12 @@
    -->
     <string name="config_defaultRotationResolverService" translatable="false"></string>
 
+    <!-- The component name for the default system AmbientContextEvent detection service.
+        This service must be trusted, as it can be activated without explicit consent of the user.
+        See android.service.ambientcontext.AmbientContextDetectionService.
+   -->
+    <string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
+
     <!-- The component name for the system-wide captions service.
          This service must be trusted, as it controls part of the UI of the volume bar.
          Example: "com.android.captions/.SystemCaptionsService"
@@ -4242,7 +4227,7 @@
     <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
 
     <!-- Default number of notifications from the same app before they are automatically grouped by the OS -->
-    <integer translatable="false" name="config_autoGroupAtCount">4</integer>
+    <integer translatable="false" name="config_autoGroupAtCount">2</integer>
 
     <!-- The OEM specified sensor type for the lift trigger to launch the camera app. -->
     <integer name="config_cameraLiftTriggerSensorType">-1</integer>
@@ -4314,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>
 
@@ -4342,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. -->
@@ -5607,4 +5594,7 @@
 
     <!-- Flag indicating if help links for Settings app should be enabled. -->
     <bool name="config_settingsHelpLinksEnabled">false</bool>
+
+    <!-- Whether or not to enable the lock screen entry point for the QR code scanner. -->
+    <bool name="config_enableQrCodeScannerOnLockScreen">false</bool>
 </resources>
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 e83f4a6..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" />
@@ -3256,6 +3257,8 @@
     <public name="gameSessionService" />
     <public name="localeConfig" />
     <public name="showBackground" />
+    <public name="inheritKeyStoreKeys" />
+    <public name="preferKeepClear" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
@@ -3279,6 +3282,8 @@
     <public name="config_systemAppProtectionService" />
     <!-- @hide @SystemApi @TestApi -->
     <public name="config_systemAutomotiveCalendarSyncManager" />
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultAutomotiveNavigation" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
@@ -3322,6 +3327,8 @@
   <staging-public-group type="bool" first-id="0x01cf0000">
     <!-- @hide @TestApi -->
     <public name="config_preventImeStartupUnlessTextEditor" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableQrCodeScannerOnLockScreen" />
   </staging-public-group>
 
   <staging-public-group type="fraction" first-id="0x01ce0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6577ebc..59ad302 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -197,7 +197,7 @@
     <string name="ThreeWCMmi">Three way calling</string>
     <string name="RuacMmi">Rejection of undesired annoying calls</string>
     <string name="CndMmi">Calling number delivery</string>
-    <string name="DndMmi">Do not disturb</string>
+    <string name="DndMmi" translatable="false">Priority mode</string>
 
     <!-- Displayed to confirm to the user that caller ID will be restricted on the next call as usual. -->
     <string name="CLIRDefaultOnNextCallOn">Caller ID defaults to restricted. Next call: Restricted</string>
@@ -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] -->
@@ -2002,9 +2029,9 @@
     <string name="permdesc_bindCarrierServices">Allows the holder to bind to carrier services. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, for applications that wish to access notification policy. -->
-    <string name="permlab_access_notification_policy">access Do Not Disturb</string>
+    <string name="permlab_access_notification_policy" translatable="false">access Priority mode</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_access_notification_policy">Allows the app to read and write Do Not Disturb configuration.</string>
+    <string name="permdesc_access_notification_policy" translatable="false">Allows the app to read and write Priority mode configuration.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_startViewPermissionUsage">start view permission usage</string>
@@ -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. -->
@@ -5262,7 +5294,7 @@
     <string name="zen_mode_forever">Until you turn off</string>
 
     <!-- Zen mode condition: no exit criteria, includes the name of the feature for emphasis. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_forever_dnd">Until you turn off Do Not Disturb</string>
+    <string name="zen_mode_forever_dnd" translatable="false">Until you turn off Priority mode</string>
 
     <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
@@ -5271,7 +5303,7 @@
     <string name="toolbar_collapse_description">Collapse</string>
 
     <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
-    <string name="zen_mode_feature_name">Do not disturb</string>
+    <string name="zen_mode_feature_name" translatable="false">Priority mode</string>
 
     <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
     <string name="zen_mode_downtime_feature_name">Downtime</string>
@@ -5687,14 +5719,14 @@
 
     <!-- Title for the notification channel notifying user of settings system changes. [CHAR LIMIT=NONE] -->
     <string name="notification_channel_system_changes">System changes</string>
-    <!-- Title for the notification channel notifying user of do not disturb system changes (i.e. Do Not Disturb has changed). [CHAR LIMIT=NONE] -->
-    <string name="notification_channel_do_not_disturb">Do Not Disturb</string>
-    <!-- Title of notification indicating do not disturb visual interruption settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_visd_title">New: Do Not Disturb is hiding notifications</string>
+    <!-- Title for the notification channel notifying user of priority mode system changes (i.e. Priority mode has changed). [CHAR LIMIT=NONE] -->
+    <string name="notification_channel_do_not_disturb" translatable="false">Priority mode</string>
+    <!-- Title of notification indicating Priority mode visual interruption settings have changed when upgrading to P -->
+    <string name="zen_upgrade_notification_visd_title" translatable="false">New: Priority mode is hiding notifications</string>
     <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
     <string name="zen_upgrade_notification_visd_content">Tap to learn more and change.</string>
-    <!-- Title of notification indicating do not disturb settings have changed when upgrading to P -->
-    <string name="zen_upgrade_notification_title">Do Not Disturb has changed</string>
+    <!-- Title of notification indicating priority mode settings have changed when upgrading to P -->
+    <string name="zen_upgrade_notification_title" translatable="false">Priority mode has changed</string>
     <!-- Content of notification indicating users can tap on the notification to go to dnd behavior settings -->
     <string name="zen_upgrade_notification_content">Tap to check what\'s blocked.</string>
 
@@ -5735,7 +5767,7 @@
     <!-- Label of notification action button to learn more about the enhanced notifications [CHAR LIMIT=20] -->
     <string name="nas_upgrade_notification_learn_more_action">Learn more</string>
     <!-- Content of notification learn more dialog about the enhanced notifications [CHAR LIMIT=NONE] -->
-    <string name="nas_upgrade_notification_learn_more_content">Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb.</string>
+    <string name="nas_upgrade_notification_learn_more_content" translatable="false">Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Priority mode.</string>
 
 
     <!-- Dynamic mode battery saver strings -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6bc35ec..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" />
@@ -3652,6 +3660,7 @@
   <java-symbol type="string" name="config_defaultRotationResolverService" />
   <java-symbol type="string" name="config_defaultSystemCaptionsService" />
   <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
+  <java-symbol type="string" name="config_defaultAmbientContextDetectionService" />
   <java-symbol type="string" name="config_retailDemoPackage" />
   <java-symbol type="string" name="config_retailDemoPackageSignature" />
 
@@ -3823,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" />
@@ -4646,4 +4653,8 @@
   <java-symbol type="string" name="config_supervisedUserCreationPackage"/>
 
   <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/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml
index d310736..fc63657 100644
--- a/core/res/res/xml/power_profile.xml
+++ b/core/res/res/xml/power_profile.xml
@@ -144,17 +144,49 @@
     <value>2</value>    <!-- 4097-/hr -->
   </array>
 
-  <!-- Cellular modem related values. Default is 0.-->
-  <item name="modem.controller.sleep">0</item>
-  <item name="modem.controller.idle">0</item>
-  <item name="modem.controller.rx">0</item>
-  <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
-    <value>0</value>
-    <value>0</value>
-    <value>0</value>
-    <value>0</value>
-    <value>0</value>
-  </array>
+  <!-- Cellular modem related values.-->
+  <modem>
+    <!-- Modem sleep drain current value in mA. -->
+    <sleep>0</sleep>
+    <!-- Modem idle drain current value in mA. -->
+    <idle>0</idle>
+    <!-- Modem active drain current values.
+         Multiple <active /> can be defined to specify current drain for different modes of
+         operation.
+         Available attributes:
+             rat - Specify the current drain for a Radio Access Technology.
+                   Available options are "LTE", "NR" and "DEFAULT".
+                   <active rat="default" /> will be used for any usage that does not match any other
+                   defined <active /> rat.
+
+             nrFrequency - Specify the current drain for a frequency level while NR is active.
+                           Available options are "LOW", "MID", "HIGH", "MMWAVE", and "DEFAULT",
+                           where,
+                           "LOW" indicated <1GHz frequencies,
+                           "MID" indicates 1GHz to 3GHz frequencies,
+                           "HIGH" indicates 3GHz to 6GHz frequencies,
+                           "MMWAVE"indicates >6GHz frequencies.
+                           <active rat="NR" nrFrequency="default"/> will be used for any usage that
+                           does not match any other defined <active rat="NR" /> nrFrequency.
+    -->
+    <active rat="DEFAULT">
+      <!-- Transmit current drain in mA. -->
+      <receive>0</receive>
+
+      <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+      <transmit level="0">0</transmit>
+      <transmit level="1">0</transmit>
+      <transmit level="2">0</transmit>
+      <transmit level="3">0</transmit>
+      <transmit level="4">0</transmit>
+    </active>
+    <!-- Additional <active /> may be defined.
+         Example:
+             <active rat="LTE"> ... </active>
+             <active rat="NR" nrFrequency="MMWAVE"> ... </active>
+             <active rat="NR" nrFrequency="DEFAULT"> ... </active>
+    -->
+  </modem>
   <item name="modem.controller.voltage">0</item>
 
   <!-- GPS related values. Default is 0.-->
@@ -163,5 +195,4 @@
     <value>0</value>
   </array>
   <item name="gps.voltage">0</item>
-
 </device>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index f2b35c7..a80424e 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
     <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
+    <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" />
     <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -64,6 +65,7 @@
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
     <uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
     <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
     <uses-permission android:name="android.permission.READ_LOGS"/>
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/res/xml/power_profile_test_modem.xml b/core/tests/coretests/res/xml/power_profile_test_modem.xml
new file mode 100644
index 0000000..ff36a9c
--- /dev/null
+++ b/core/tests/coretests/res/xml/power_profile_test_modem.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<device name="test">
+    <test-modem name="testModemPowerProfile_defaultRat">
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>10</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>20</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>30</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">40</transmit>
+            <transmit level="1">50</transmit>
+            <transmit level="2">60</transmit>
+            <transmit level="3">70</transmit>
+            <transmit level="4">80</transmit>
+        </active>
+    </test-modem>
+
+    <test-modem name="testModemPowerProfile_partiallyDefined">
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>1</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>2</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>3</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">4</transmit>
+            <transmit level="1">5</transmit>
+            <transmit level="2">6</transmit>
+            <transmit level="3">7</transmit>
+            <transmit level="4">8</transmit>
+        </active>
+        <active rat="NR" nrFrequency="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>13</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">14</transmit>
+            <transmit level="1">15</transmit>
+            <transmit level="2">16</transmit>
+            <transmit level="3">17</transmit>
+            <transmit level="4">18</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MMWAVE">
+            <!-- Transmit current drain in mA. -->
+            <receive>53</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">54</transmit>
+            <transmit level="1">55</transmit>
+            <transmit level="2">56</transmit>
+            <transmit level="3">57</transmit>
+            <transmit level="4">58</transmit>
+        </active>
+    </test-modem>
+
+    <test-modem name="testModemPowerProfile_fullyDefined">
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>1</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>2</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>3</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">4</transmit>
+            <transmit level="1">5</transmit>
+            <transmit level="2">6</transmit>
+            <transmit level="3">7</transmit>
+            <transmit level="4">8</transmit>
+        </active>
+        <active rat="LTE">
+            <!-- Transmit current drain in mA. -->
+            <receive>10</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">20</transmit>
+            <transmit level="1">30</transmit>
+            <transmit level="2">40</transmit>
+            <transmit level="3">50</transmit>
+            <transmit level="4">60</transmit>
+        </active>
+        <active rat="NR" nrFrequency="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>13</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">14</transmit>
+            <transmit level="1">15</transmit>
+            <transmit level="2">16</transmit>
+            <transmit level="3">17</transmit>
+            <transmit level="4">18</transmit>
+        </active>
+        <active rat="NR" nrFrequency="LOW">
+            <!-- Transmit current drain in mA. -->
+            <receive>23</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">24</transmit>
+            <transmit level="1">25</transmit>
+            <transmit level="2">26</transmit>
+            <transmit level="3">27</transmit>
+            <transmit level="4">28</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MID">
+            <!-- Transmit current drain in mA. -->
+            <receive>33</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">34</transmit>
+            <transmit level="1">35</transmit>
+            <transmit level="2">36</transmit>
+            <transmit level="3">37</transmit>
+            <transmit level="4">38</transmit>
+        </active>
+        <active rat="NR" nrFrequency="HIGH">
+            <!-- Transmit current drain in mA. -->
+            <receive>43</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">44</transmit>
+            <transmit level="1">45</transmit>
+            <transmit level="2">46</transmit>
+            <transmit level="3">47</transmit>
+            <transmit level="4">48</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MMWAVE">
+            <!-- Transmit current drain in mA. -->
+            <receive>53</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">54</transmit>
+            <transmit level="1">55</transmit>
+            <transmit level="2">56</transmit>
+            <transmit level="3">57</transmit>
+            <transmit level="4">58</transmit>
+        </active>
+    </test-modem>
+</device>
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/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
index 0cfcd8f8..b66642c 100644
--- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
+++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java
@@ -16,11 +16,17 @@
 
 package android.content.pm;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL;
+
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -58,6 +64,8 @@
     @Mock
     private UserManager mUserManager;
     @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
     private ICrossProfileApps mService;
     @Mock
     private Resources mResources;
@@ -75,6 +83,10 @@
         when(mContext.getPackageName()).thenReturn(MY_PACKAGE);
         when(mContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mContext.getSystemServiceName(DevicePolicyManager.class)).thenReturn(
+                Context.DEVICE_POLICY_SERVICE);
+        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mDevicePolicyManager);
     }
 
     @Before
@@ -98,7 +110,7 @@
         setValidTargetProfile(MANAGED_PROFILE);
 
         mCrossProfileApps.getProfileSwitchingLabel(MANAGED_PROFILE);
-        verify(mResources).getString(R.string.managed_profile_label);
+        verify(mDevicePolicyManager).getString(eq(SWITCH_TO_WORK_LABEL), any());
     }
 
     @Test
@@ -106,7 +118,7 @@
         setValidTargetProfile(PERSONAL_PROFILE);
 
         mCrossProfileApps.getProfileSwitchingLabel(PERSONAL_PROFILE);
-        verify(mResources).getString(R.string.user_owner_label);
+        verify(mDevicePolicyManager).getString(eq(SWITCH_TO_PERSONAL_LABEL), any());
     }
 
     @Test(expected = SecurityException.class)
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/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
index 2dd3f69..ba9c8d9 100644
--- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
@@ -64,12 +64,12 @@
     }
 
     @Test
-    public void testAdd() {
+    public void testIncrementValue() {
         final SparseDoubleArray sda = new SparseDoubleArray();
 
         sda.put(4, 6.1);
-        sda.add(4, -1.2);
-        sda.add(2, -1.2);
+        sda.incrementValue(4, -1.2);
+        sda.incrementValue(2, -1.2);
 
         assertEquals(6.1 - 1.2, sda.get(4), PRECISION);
         assertEquals(-1.2, sda.get(2), PRECISION);
diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
index df2d752..b29b6f1 100644
--- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
@@ -154,4 +154,16 @@
         assertRemoved(startIndex, endIndex);
         assertTrue(isSame(sparseLongArray2, mSparseLongArray));
     }
+
+    @Test
+    public void testIncrementValue() {
+        final SparseLongArray sla = new SparseLongArray();
+
+        sla.put(4, 6);
+        sla.incrementValue(4, 4);
+        sla.incrementValue(2, 5);
+
+        assertEquals(6 + 4, sla.get(4));
+        assertEquals(5, sla.get(2));
+    }
 }
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/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
index 8f04461..5ea9199 100644
--- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java
@@ -33,7 +33,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,7 +56,6 @@
     private static final int TOUCH_SLOP = 8;
     private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
     private static final Rect sHwArea = new Rect(100, 200, 500, 500);
-    private static final EditorInfo sFakeEditorInfo = new EditorInfo();
 
     private HandwritingInitiator mHandwritingInitiator;
     private View mTestView;
@@ -72,7 +70,6 @@
         InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class);
         mHandwritingInitiator =
                 spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
-        mHandwritingInitiator.updateEditorBound(sHwArea);
 
         // mock a parent so that HandwritingInitiator can get
         ViewGroup parent = new ViewGroup(context) {
@@ -82,10 +79,7 @@
             }
             @Override
             public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
-                r.left = sHwArea.left;
-                r.top = sHwArea.top;
-                r.right = sHwArea.right;
-                r.bottom = sHwArea.bottom;
+                r.set(sHwArea);
                 return true;
             }
         };
@@ -97,7 +91,7 @@
 
     @Test
     public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         final int x1 = (sHwArea.left + sHwArea.right) / 2;
         final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -109,13 +103,13 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // Stylus movement win HandwritingArea should trigger IMM.startHandwriting once.
+        // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
     }
 
     @Test
     public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         final int x1 = (sHwArea.left + sHwArea.right) / 2;
         final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -152,14 +146,14 @@
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
         // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
 
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
     }
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         final int x1 = 200;
         final int y1 = 200;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -175,7 +169,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         final int x1 = 10;
         final int y1 = 10;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -191,7 +185,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTapTimeOut() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         final int x1 = 10;
         final int y1 = 10;
         final long time1 = 10L;
@@ -210,18 +204,17 @@
 
     @Test
     public void onInputConnectionCreated_inputConnectionCreated() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
     }
 
     @Test
     public void onInputConnectionCreated_inputConnectionClosed() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         mHandwritingInitiator.onInputConnectionClosed(mTestView);
 
         assertThat(mHandwritingInitiator.mConnectedView).isNull();
-        assertThat(mHandwritingInitiator.mEditorBound).isNull();
     }
 
     @Test
@@ -229,22 +222,14 @@
         // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
         // called before View#onInputConnectionClosedInternal. As a result, we need to handle the
         // case where "one view "2 InputConnections".
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
-        mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
+        mHandwritingInitiator.onInputConnectionCreated(mTestView);
         mHandwritingInitiator.onInputConnectionClosed(mTestView);
 
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView);
     }
 
-    @Test
-    public void updateEditorBound() {
-        Rect rect = new Rect(1, 2, 3, 4);
-        mHandwritingInitiator.updateEditorBound(rect);
-
-        assertThat(mHandwritingInitiator.mEditorBound).isEqualTo(rect);
-    }
-
     private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) {
         MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
         properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS;
diff --git a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java b/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java
deleted file mode 100644
index c15fc3a..0000000
--- a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.view;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class SurfaceControlFpsListenerTest {
-
-    @Test
-    public void registersAndUnregisters() {
-
-        SurfaceControlFpsListener listener = new SurfaceControlFpsListener() {
-            @Override
-            public void onFpsReported(float fps) {
-                // Ignore
-            }
-        };
-
-        listener.register(0);
-
-        listener.unregister();
-    }
-}
diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
new file mode 100644
index 0000000..bf508db
--- /dev/null
+++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.window;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class TaskFpsCallbackTest {
+
+    private Context mContext;
+    private WindowManager mWindowManager;
+    private ActivityTaskManager mActivityTaskManager;
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+    }
+
+    @Test
+    public void testRegisterAndUnregister() {
+
+        final TaskFpsCallback.OnFpsCallbackListener listener = fps -> {
+            // Ignore
+        };
+        final TaskFpsCallback callback = new TaskFpsCallback(Runnable::run, listener);
+
+        final List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1);
+        assertEquals(tasks.size(), 1);
+        mWindowManager.registerTaskFpsCallback(tasks.get(0).taskId, callback);
+        mWindowManager.unregisterTaskFpsCallback(callback);
+    }
+}
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/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
new file mode 100644
index 0000000..a1a1e20
--- /dev/null
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.window;
+
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindow;
+import android.view.IWindowSession;
+import android.view.OnBackInvokedCallback;
+import android.view.OnBackInvokedDispatcher;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+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;
+
+/**
+ * Tests for {@link WindowOnBackInvokedDispatcherTest}
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowOnBackInvokedDispatcherTest {
+    @Mock
+    private IWindowSession mWindowSession;
+    @Mock
+    private IWindow mWindow;
+    private WindowOnBackInvokedDispatcher mDispatcher;
+    @Mock
+    private OnBackInvokedCallback mCallback1;
+    @Mock
+    private OnBackInvokedCallback mCallback2;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mDispatcher = new WindowOnBackInvokedDispatcher();
+        mDispatcher.attachToWindow(mWindowSession, mWindow);
+    }
+
+    private void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    @Test
+    public void propagatesTopCallback_samePriority() throws RemoteException {
+        ArgumentCaptor<IOnBackInvokedCallback> captor =
+                ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
+
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+
+        verify(mWindowSession, times(2))
+                .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
+        captor.getAllValues().get(0).onBackStarted();
+        waitForIdle();
+        verify(mCallback1).onBackStarted();
+        verifyZeroInteractions(mCallback2);
+
+        captor.getAllValues().get(1).onBackStarted();
+        waitForIdle();
+        verify(mCallback2).onBackStarted();
+        verifyNoMoreInteractions(mCallback1);
+    }
+
+    @Test
+    public void propagatesTopCallback_differentPriority() throws RemoteException {
+        ArgumentCaptor<IOnBackInvokedCallback> captor =
+                ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
+
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+
+        verify(mWindowSession)
+                .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
+        verifyNoMoreInteractions(mWindowSession);
+        captor.getValue().onBackStarted();
+        waitForIdle();
+        verify(mCallback1).onBackStarted();
+    }
+
+    @Test
+    public void propagatesTopCallback_withRemoval() throws RemoteException {
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+
+        reset(mWindowSession);
+        mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
+        verifyZeroInteractions(mWindowSession);
+
+        mDispatcher.unregisterOnBackInvokedCallback(mCallback2);
+        verify(mWindowSession).setOnBackInvokedCallback(Mockito.eq(mWindow), isNull());
+    }
+
+
+    @Test
+    public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException {
+        ArgumentCaptor<IOnBackInvokedCallback> captor =
+                ArgumentCaptor.forClass(IOnBackInvokedCallback.class);
+
+        mDispatcher.registerOnBackInvokedCallback(mCallback1,
+                OnBackInvokedDispatcher.PRIORITY_OVERLAY);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+
+        reset(mWindowSession);
+        mDispatcher.registerOnBackInvokedCallback(
+                mCallback2, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
+        verify(mWindowSession)
+                .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture());
+        captor.getValue().onBackStarted();
+        waitForIdle();
+        verify(mCallback2).onBackStarted();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 43590ba..1f6b57e 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -297,7 +297,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector).showToast(anyInt(), anyInt());
+        verify(sInjector).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -312,7 +312,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -324,7 +324,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -336,7 +336,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -348,7 +348,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -360,7 +360,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -372,7 +372,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector).showToast(anyInt(), anyInt());
+        verify(sInjector).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -386,7 +386,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -399,7 +399,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -412,7 +412,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -425,7 +425,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -438,7 +438,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -452,7 +452,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -466,7 +466,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -480,7 +480,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -494,7 +494,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -507,7 +507,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector).showToast(anyInt(), anyInt());
+        verify(sInjector).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -521,7 +521,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector).showToast(anyInt(), anyInt());
+        verify(sInjector).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -535,7 +535,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector).showToast(anyInt(), anyInt());
+        verify(sInjector).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -551,7 +551,7 @@
         mActivityRule.launchActivity(intent);
 
         verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
-        verify(sInjector, never()).showToast(anyInt(), anyInt());
+        verify(sInjector, never()).showToast(anyString(), anyInt());
     }
 
     @Test
@@ -692,6 +692,6 @@
         }
 
         @Override
-        public void showToast(int messageId, int duration) {}
+        public void showToast(String message, int duration) {}
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 388cf6e..be8045d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,8 +37,12 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.UidTraffic;
 import android.os.BatteryStats;
+import android.os.BluetoothBatteryStats;
 import android.os.WakeLockStats;
+import android.os.WorkSource;
 import android.util.SparseArray;
 import android.view.Display;
 
@@ -47,6 +51,8 @@
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,6 +72,8 @@
     private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader;
     @Mock
     private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+    @Mock
+    private PowerProfile mPowerProfile;
 
     private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStatsImpl;
@@ -79,6 +87,7 @@
         when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
         mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
+                .setPowerProfile(mPowerProfile)
                 .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
                 .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
     }
@@ -559,4 +568,38 @@
         assertThat(wakeLock2.timeHeldMs).isEqualTo(3000);  // 9000-6000
         assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000)
     }
+
+    @Test
+    public void testGetBluetoothBatteryStats() {
+        when(mPowerProfile.getAveragePower(
+                PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0);
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+
+        final WorkSource ws = new WorkSource(10042);
+        mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, false, 1000, 1000);
+        mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, false, 5000, 5000);
+        mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, true, 6000, 6000);
+        mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000);
+        mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000);
+
+        BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0);
+        info.setUidTraffic(ImmutableList.of(
+                new UidTraffic(10042, 3000, 4000),
+                new UidTraffic(10043, 5000, 8000)));
+        mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000);
+
+        BluetoothBatteryStats stats =
+                mBatteryStatsImpl.getBluetoothBatteryStats();
+        assertThat(stats.getUidStats()).hasSize(2);
+
+        final BluetoothBatteryStats.UidStats uidStats =
+                stats.getUidStats().stream().filter(u -> u.uid == 10042).findFirst().get();
+        assertThat(uidStats.scanTimeMs).isEqualTo(7000);  // 4000+3000
+        assertThat(uidStats.unoptimizedScanTimeMs).isEqualTo(3000);
+        assertThat(uidStats.scanResultCount).isEqualTo(42);
+        assertThat(uidStats.rxTimeMs).isEqualTo(7375);  // Some scan time is treated as RX
+        assertThat(uidStats.txTimeMs).isEqualTo(7666);  // Some scan time is treated as TX
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 9699275..8cc4c34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -83,7 +83,7 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(6000);
+        assertThat(parcel.dataSize()).isLessThan(7000);
 
         parcel.setDataPosition(0);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index d361da9..ed035e5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -25,18 +25,21 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
+import android.os.UidBatteryConsumer;
+import android.os.WorkSource;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class BluetoothPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
@@ -50,6 +53,12 @@
 
     @Test
     public void testTimerBasedModel() {
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        final WorkSource ws = new WorkSource(APP_UID);
+        batteryStats.noteBluetoothScanStartedFromSourceLocked(ws, false, 0, 0);
+        batteryStats.noteBluetoothScanStoppedFromSourceLocked(ws, false, 1000, 1000);
+
         setupBluetoothEnergyInfo(0, BatteryStats.POWER_DATA_UNAVAILABLE);
 
         BluetoothPowerCalculator calculator =
@@ -57,8 +66,81 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386,
-                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.06944, 3000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(APP_UID),
+                0.19444, 9000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testTimerBasedModel_byProcessState() {
+        mStatsRule.setTime(1000, 1000);
+
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
+        info1.setUidTraffic(ImmutableList.of(
+                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                new UidTraffic(APP_UID, 3000, 4000)));
+
+        batteryStats.updateBluetoothStateLocked(info1,
+                0/*1_000_000*/, 2000, 2000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000);
+
+        BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000);
+        info2.setUidTraffic(ImmutableList.of(
+                new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
+                new UidTraffic(APP_UID, 7000, 8000)));
+
+        batteryStats.updateBluetoothStateLocked(info2,
+                0 /*5_000_000 */, 4000, 4000);
+
+        BluetoothPowerCalculator calculator =
+                new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .powerProfileModeledOnly()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isEqualTo(6166);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isWithin(PRECISION).of(0.1226666);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.081);
+        assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.0416666);
+        assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
     }
 
     @Test
@@ -71,8 +153,18 @@
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(),
                 calculator);
 
-        assertCalculatedPower(0.08216, 0.18169, 0.30030, 0.26386,
-                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(APP_UID),
+                0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.30030, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -85,11 +177,85 @@
 
         mStatsRule.apply(calculator);
 
-        assertCalculatedPower(0.10378, 0.22950, 0.33333, 0.33329,
-                BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(APP_UID),
+                0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     @Test
+    public void testMeasuredEnergyBasedModel_byProcessState() {
+        mStatsRule.initMeasuredEnergyStatsLocked();
+        mStatsRule.setTime(1000, 1000);
+
+        BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+
+        BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
+        info1.setUidTraffic(ImmutableList.of(
+                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                new UidTraffic(APP_UID, 3000, 4000)));
+
+        batteryStats.updateBluetoothStateLocked(info1,
+                1_000_000, 2000, 2000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000);
+
+        BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000);
+        info2.setUidTraffic(ImmutableList.of(
+                new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
+                new UidTraffic(APP_UID, 7000, 8000)));
+
+        batteryStats.updateBluetoothStateLocked(info2,
+                5_000_000, 4000, 4000);
+
+        BluetoothPowerCalculator calculator =
+                new BluetoothPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isEqualTo(6166);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isWithin(PRECISION).of(0.8220561);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.4965352);
+        assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.3255208);
+        assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
+    }
+
+
+    @Test
     public void testIgnoreMeasuredEnergyBasedModel() {
         mStatsRule.initMeasuredEnergyStatsLocked();
         setupBluetoothEnergyInfo(4000000, 1200000);
@@ -99,38 +265,31 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        assertCalculatedPower(0.08216, 0.18169, 0.26388, 0.26386,
-                BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(APP_UID),
+                0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
         final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
                 BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0,
                 reportedEnergyUc);
-        info.setUidTraffic(new ArrayList<UidTraffic>(){{
-                add(new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000));
-                add(new UidTraffic(APP_UID, 3000, 4000));
-            }});
+        info.setUidTraffic(ImmutableList.of(
+                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                new UidTraffic(APP_UID, 3000, 4000)));
         mStatsRule.getBatteryStats().updateBluetoothStateLocked(info,
                 consumedEnergyUc, 1000, 1000);
     }
 
-    private void assertCalculatedPower(double bluetoothUidPowerMah, double appPowerMah,
-            double devicePowerMah, double allAppsPowerMah, int powerModelPowerProfile) {
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                bluetoothUidPowerMah, 3583, powerModelPowerProfile);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getUidBatteryConsumer(APP_UID),
-                appPowerMah, 8416, powerModelPowerProfile);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getDeviceBatteryConsumer(),
-                devicePowerMah, 12000, powerModelPowerProfile);
-        assertBluetoothPowerAndDuration(
-                mStatsRule.getAppsBatteryConsumer(),
-                allAppsPowerMah, 11999, powerModelPowerProfile);
-    }
-
     private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
             double powerMah, int durationMs, @BatteryConsumer.PowerModel int powerModel) {
         assertThat(batteryConsumer).isNotNull();
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index bddb3a1..1bb41a8 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -43,6 +43,7 @@
  */
 public class MockBatteryStatsImpl extends BatteryStatsImpl {
     public boolean mForceOnBattery;
+    // The mNetworkStats will be used for both wifi and mobile categories
     private NetworkStats mNetworkStats;
     private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync();
 
@@ -118,11 +119,16 @@
     }
 
     @Override
-    protected NetworkStats readNetworkStatsLocked(@NonNull NetworkStatsManager networkStatsManager,
-            String[] ifaces) {
+    protected NetworkStats readMobileNetworkStatsLocked(
+            @NonNull NetworkStatsManager networkStatsManager) {
         return mNetworkStats;
     }
 
+    @Override
+    protected NetworkStats readWifiNetworkStatsLocked(
+            @NonNull NetworkStatsManager networkStatsManager) {
+        return mNetworkStats;
+    }
     public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) {
         mPowerProfile = powerProfile;
         return this;
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 88ee405..bc3b422 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -21,29 +21,49 @@
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
 
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.frameworks.coretests.R;
+import com.android.internal.power.ModemPowerProfile;
+import com.android.internal.util.XmlUtils;
+
 import junit.framework.TestCase;
 
 import org.junit.Before;
 import org.junit.Test;
 
 /*
- * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml
+ * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml and
+ * frameworks/base/core/tests/coretests/res/xml/power_profile_test_modem.xml
+ *
+ * Run with:
+ *     atest com.android.internal.os.PowerProfileTest
  */
 @SmallTest
 public class PowerProfileTest extends TestCase {
 
+    static final String TAG_TEST_MODEM = "test-modem";
+    static final String ATTR_NAME = "name";
+
     private PowerProfile mProfile;
+    private Context mContext;
 
     @Before
     public void setUp() {
-        mProfile = new PowerProfile(InstrumentationRegistry.getContext(), true);
+        mContext = InstrumentationRegistry.getContext();
+        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));
@@ -65,6 +85,435 @@
                 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
+    public void testModemPowerProfile_defaultRat() throws Exception {
+        final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+                "testModemPowerProfile_defaultRat");
+        ModemPowerProfile mpp = new ModemPowerProfile();
+        mpp.parseFromXml(parser);
+        assertEquals(10.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP));
+        assertEquals(20.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE));
+
+        // Only default RAT was defined, all other RAT's should fallback to the default value.
+        assertEquals(30.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(30.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(30.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+
+        assertEquals(40.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(40.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(40.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+
+        assertEquals(50.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(50.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(50.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+
+        assertEquals(60.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(60.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(60.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+
+        assertEquals(70.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(70.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(70.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+
+        assertEquals(80.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+        assertEquals(80.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+        assertEquals(80.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+    }
+
+    @Test
+    public void testModemPowerProfile_partiallyDefined() throws Exception {
+        final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+                "testModemPowerProfile_partiallyDefined");
+        ModemPowerProfile mpp = new ModemPowerProfile();
+        mpp.parseFromXml(parser);
+        assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP));
+        assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE));
+
+        assertEquals(3.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(4.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(5.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(6.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(7.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(8.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        // LTE RAT power constants were not defined, fallback to defaults
+        assertEquals(3.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(4.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(5.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(6.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(7.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(8.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        // Non-mmwave NR frequency power constants were not defined, fallback to defaults
+        assertEquals(13.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(14.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(15.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(16.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(17.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(18.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(13.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(14.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(15.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(16.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(17.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(18.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+    }
+
+    @Test
+    public void testModemPowerProfile_fullyDefined() throws Exception {
+        final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem,
+                "testModemPowerProfile_fullyDefined");
+        ModemPowerProfile mpp = new ModemPowerProfile();
+        mpp.parseFromXml(parser);
+        assertEquals(1.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP));
+        assertEquals(2.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE));
+
+        // Only default RAT was defined, all other RAT's should fallback to the default value.
+        assertEquals(3.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(4.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(5.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(6.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(7.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(8.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(10.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(20.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(30.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(40.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(50.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(60.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_LTE | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(13.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(14.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(15.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(16.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(17.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(18.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(23.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(24.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(25.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(26.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(27.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(28.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(33.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(34.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(35.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(36.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(37.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(38.0, mpp.getAverageBatteryDrainMa(
+                ModemPowerProfile.MODEM_RAT_TYPE_NR | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID
+                        | ModemPowerProfile.MODEM_DRAIN_TYPE_TX
+                        | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(43.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(44.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(45.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(46.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(47.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(48.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+
+        assertEquals(53.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_RX));
+        assertEquals(54.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_0));
+        assertEquals(55.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_1));
+        assertEquals(56.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_2));
+        assertEquals(57.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_3));
+        assertEquals(58.0, mpp.getAverageBatteryDrainMa(ModemPowerProfile.MODEM_RAT_TYPE_NR
+                | ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE
+                | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4));
+    }
+
+    private XmlResourceParser getTestModemElement(@XmlRes int xmlId, String elementName)
+            throws Exception {
+        final String element = TAG_TEST_MODEM;
+        final Resources resources = mContext.getResources();
+        XmlResourceParser parser = resources.getXml(xmlId);
+        while (true) {
+            XmlUtils.nextElement(parser);
+            final String e = parser.getName();
+            if (e == null) break;
+            if (!e.equals(element)) continue;
+
+            final String name = parser.getAttributeValue(null, ATTR_NAME);
+            if (!name.equals(elementName)) continue;
+
+            return parser;
+        }
+        fail("Unanable to find element " + element + " with name " + elementName);
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
index a787357..a368399 100644
--- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
@@ -92,7 +92,10 @@
         final BatteryStatsImpl batteryStats = setupTestNetworkNumbers();
         final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo();
 
-        batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 1000, 1000,
+        batteryStats.noteWifiScanStartedLocked(APP_UID, 500, 500);
+        batteryStats.noteWifiScanStoppedLocked(APP_UID, 1500, 1500);
+
+        batteryStats.updateWifiState(energyInfo, POWER_DATA_UNAVAILABLE, 2000, 2000,
                 mNetworkStatsManager);
 
         WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile());
@@ -100,15 +103,15 @@
 
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(1423);
+                .isEqualTo(2473);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isWithin(PRECISION).of(0.2214666);
+                .isWithin(PRECISION).of(0.3964);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(4002);
+                .isEqualTo(4001);
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.86666);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index 3fdb0da..ddcab6e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -30,6 +30,7 @@
         <permission name="android.permission.MANAGE_DEBUGGING"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
+        <permission name="android.permission.MANAGE_GAME_MODE" />
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
@@ -59,5 +60,6 @@
         <permission name="android.permission.READ_DREAM_STATE"/>
         <permission name="android.permission.READ_DREAM_SUPPRESSION"/>
         <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
+        <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index f2a33de..d95644a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -30,6 +30,7 @@
         <permission name="android.permission.GET_APP_OPS_STATS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_DEBUGGING"/>
+        <permission name="android.permission.MANAGE_GAME_MODE" />
         <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MANAGE_USERS"/>
@@ -50,6 +51,7 @@
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REQUEST_NETWORK_SCORES"/>
         <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+        <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
         <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
         <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
@@ -71,5 +73,6 @@
         <permission name="android.permission.USE_BACKGROUND_BLUR" />
         <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
         <permission name="android.permission.FORCE_STOP_PACKAGES" />
+        <permission name="android.permission.ACCESS_FPS_COUNTER" />
     </privapp-permissions>
 </permissions>
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 6f5951b..0e06fac 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -333,6 +333,7 @@
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+        <permission name="android.permission.MANAGE_GAME_MODE"/>
         <permission name="android.permission.MANAGE_ROLLBACKS"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
@@ -394,6 +395,9 @@
         <permission name="android.permission.SET_WALLPAPER_COMPONENT" />
         <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
         <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
+        <!-- Permission required for CTS test - TrustTestCases -->
+        <permission name="android.permission.PROVIDE_TRUST_AGENT" />
+        <permission name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
         <!-- Permissions required for Incremental CTS tests -->
         <permission name="com.android.permission.USE_INSTALLER_V2"/>
         <permission name="android.permission.LOADER_USAGE_STATS"/>
@@ -524,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">
@@ -571,6 +576,7 @@
     <privapp-permissions package="com.android.settings">
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/>
+        <permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.bips">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 535d656..1567233 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -103,18 +103,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
-    "-2002500255": {
-      "message": "Defer removing snapshot surface in %dms",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
-    },
-    "-1991255017": {
-      "message": "Drawing snapshot surface sizeMismatch=%b",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
-    },
     "-1980468143": {
       "message": "DisplayArea appeared name=%s",
       "level": "VERBOSE",
@@ -331,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",
@@ -361,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",
@@ -457,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",
@@ -505,12 +499,6 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-1556507536": {
-      "message": "Passing transform hint %d for window %s%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-1554521902": {
       "message": "showInsets(ime) was requested by different window: %s ",
       "level": "WARN",
@@ -721,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",
@@ -745,6 +727,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1343787701": {
+      "message": "startBackNavigation task=%s, topRunningActivity=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "-1340540100": {
       "message": "Creating SnapshotStartingData",
       "level": "VERBOSE",
@@ -1183,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",
@@ -1249,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",
@@ -1561,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",
@@ -1597,12 +1579,6 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "-405536909": {
-      "message": "Removing snapshot surface",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
-    },
     "-401282500": {
       "message": "destroyIfPossible: r=%s destroy returned removed=%s",
       "level": "DEBUG",
@@ -1741,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",
@@ -1867,6 +1837,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-134091882": {
+      "message": "Screenshotting Activity %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-124316973": {
       "message": "Translucent=%s Floating=%s ShowWallpaper=%s Disable=%s",
       "level": "VERBOSE",
@@ -1951,6 +1927,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
       "at": "com\/android\/server\/wm\/WindowContainer.java"
     },
+    "-23020844": {
+      "message": "Back: Reset surfaces",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "-21399771": {
       "message": "activity %s already destroying, skipping request with reason:%s",
       "level": "VERBOSE",
@@ -2005,12 +1987,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "44438983": {
-      "message": "performLayout: Activity exiting now removed %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "45285419": {
       "message": "startingWindow was set but startingSurface==null, couldn't remove",
       "level": "VERBOSE",
@@ -2083,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",
@@ -2419,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",
@@ -2443,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",
@@ -2797,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",
@@ -3091,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",
@@ -3211,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",
@@ -3271,12 +3259,6 @@
       "group": "WM_DEBUG_LAYER_MIRRORING",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "1417601133": {
-      "message": "Enqueueing ADD_STARTING",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1422781269": {
       "message": "Resuming rotation after re-position",
       "level": "DEBUG",
@@ -3397,6 +3379,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1554795024": {
+      "message": "Previous Activity is %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_BACK_PREVIEW",
+      "at": "com\/android\/server\/wm\/BackNavigationController.java"
+    },
     "1557732761": {
       "message": "For Intent %s bringing to top: %s",
       "level": "DEBUG",
@@ -3697,12 +3685,6 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java"
     },
-    "1884961873": {
-      "message": "Sleep still need to stop %d activities",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/Task.java"
-    },
     "1891501279": {
       "message": "cancelAnimation(): reason=%s",
       "level": "DEBUG",
@@ -3835,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",
@@ -3924,6 +3912,9 @@
     "WM_DEBUG_APP_TRANSITIONS_ANIM": {
       "tag": "WindowManager"
     },
+    "WM_DEBUG_BACK_PREVIEW": {
+      "tag": "CoreBackPreview"
+    },
     "WM_DEBUG_BOOT": {
       "tag": "WindowManager"
     },
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/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index a954344..8811a7f 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -20,7 +20,6 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.security.maintenance.UserState;
-import android.system.keystore2.Domain;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -120,15 +119,6 @@
     }
 
     /**
-     * Forwards the request to clear a UID to Keystore 2.0.
-     * @hide
-     */
-    public boolean clearUid(int uid) {
-        return AndroidKeyStoreMaintenance.clearNamespace(Domain.APP, uid) == 0;
-    }
-
-
-    /**
      * Add an authentication record to the keystore authorization table.
      *
      * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
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/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 0cdaa20..1b8032b 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -43,6 +43,9 @@
     <!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
     <fraction name="config_pipShortestEdgePercent">40%</fraction>
 
+    <!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. -->
+    <bool name="config_pipEnableEnterSplitButton">false</bool>
+
     <!-- Animation duration when using long press on recents to dock -->
     <integer name="long_press_dock_anim_duration">250</integer>
 
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/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
new file mode 100644
index 0000000..b310dd6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.wm.shell.back;
+
+import android.view.MotionEvent;
+
+/**
+ * Interface for SysUI to get access to the Back animation related methods.
+ */
+public interface BackAnimation {
+
+    /**
+     * Called when a {@link MotionEvent} is generated by a back gesture.
+     */
+    void onBackMotion(MotionEvent event);
+
+    /**
+     * Sets whether the back gesture is past the trigger threshold or not.
+     */
+    void setTriggerBack(boolean triggerBack);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
new file mode 100644
index 0000000..229e8ee0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -0,0 +1,286 @@
+/*
+ * 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.wm.shell.back;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.window.BackNavigationInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the window animation run when a user initiates a back gesture.
+ */
+public class BackAnimationController {
+
+    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+    public static final boolean IS_ENABLED = SystemProperties
+            .getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
+    private static final String TAG = "BackAnimationController";
+
+    /**
+     * Location of the initial touch event of the back gesture.
+     */
+    private final PointF mInitTouchLocation = new PointF();
+
+    /**
+     * Raw delta between {@link #mInitTouchLocation} and the last touch location.
+     */
+    private final Point mTouchEventDelta = new Point();
+    private final ShellExecutor mShellExecutor;
+
+    /** True when a back gesture is ongoing */
+    private boolean mBackGestureStarted = false;
+
+    /** @see #setTriggerBack(boolean) */
+    private boolean mTriggerBack;
+
+    @Nullable
+    private BackNavigationInfo mBackNavigationInfo;
+    private final SurfaceControl.Transaction mTransaction;
+    private final IActivityTaskManager mActivityTaskManager;
+
+    public BackAnimationController(@ShellMainThread ShellExecutor shellExecutor) {
+        this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService());
+    }
+
+    @VisibleForTesting
+    BackAnimationController(@NonNull ShellExecutor shellExecutor,
+            @NonNull SurfaceControl.Transaction transaction,
+            @NonNull IActivityTaskManager activityTaskManager) {
+        mShellExecutor = shellExecutor;
+        mTransaction = transaction;
+        mActivityTaskManager = activityTaskManager;
+    }
+
+    public BackAnimation getBackAnimationImpl() {
+        return mBackAnimation;
+    }
+
+    private final BackAnimation mBackAnimation = new BackAnimationImpl();
+
+    private class BackAnimationImpl implements BackAnimation {
+
+        @Override
+        public void onBackMotion(MotionEvent event) {
+            mShellExecutor.execute(() -> onMotionEvent(event));
+        }
+
+        @Override
+        public void setTriggerBack(boolean triggerBack) {
+            mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack));
+        }
+    }
+
+    /**
+     * Called when a new motion event needs to be transferred to this
+     * {@link BackAnimationController}
+     */
+    public void onMotionEvent(MotionEvent event) {
+        int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            initAnimation(event);
+        } else if (action == MotionEvent.ACTION_MOVE) {
+            onMove(event);
+        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            onGestureFinished();
+        }
+    }
+
+    private void initAnimation(MotionEvent event) {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
+        if (mBackGestureStarted) {
+            Log.e(TAG, "Animation is being initialized but is already started.");
+            return;
+        }
+
+        if (mBackNavigationInfo != null) {
+            finishAnimation();
+        }
+        mInitTouchLocation.set(event.getX(), event.getY());
+        mBackGestureStarted = true;
+
+        try {
+            mBackNavigationInfo = mActivityTaskManager.startBackNavigation();
+            onBackNavigationInfoReceived(mBackNavigationInfo);
+        } catch (RemoteException remoteException) {
+            Log.e(TAG, "Failed to initAnimation", remoteException);
+            finishAnimation();
+        }
+    }
+
+    private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) {
+        if (backNavigationInfo == null
+                || backNavigationInfo.getDepartingWindowContainer() == null) {
+            Log.e(TAG, "Received BackNavigationInfo is null.");
+            finishAnimation();
+            return;
+        }
+
+        HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
+        if (hardwareBuffer != null) {
+            displayTargetScreenshot(hardwareBuffer,
+                    backNavigationInfo.getTaskWindowConfiguration());
+        }
+        mTransaction.apply();
+    }
+
+    /**
+     * Display the screenshot of the activity beneath.
+     *
+     * @param hardwareBuffer The buffer containing the screenshot.
+     */
+    private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer,
+            WindowConfiguration taskWindowConfiguration) {
+        SurfaceControl screenshotSurface =
+                mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface();
+        if (screenshotSurface == null) {
+            Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. ");
+            return;
+        }
+
+        // Scale the buffer to fill the whole Task
+        float sx = 1;
+        float sy = 1;
+        float w = taskWindowConfiguration.getBounds().width();
+        float h = taskWindowConfiguration.getBounds().height();
+
+        if (w != hardwareBuffer.getWidth()) {
+            sx = w / hardwareBuffer.getWidth();
+        }
+
+        if (h != hardwareBuffer.getHeight()) {
+            sy = h / hardwareBuffer.getHeight();
+        }
+        mTransaction.setScale(screenshotSurface, sx, sy);
+        mTransaction.setBuffer(screenshotSurface, hardwareBuffer);
+        mTransaction.setVisibility(screenshotSurface, true);
+    }
+
+    private void onMove(MotionEvent event) {
+        if (!mBackGestureStarted || mBackNavigationInfo == null) {
+            return;
+        }
+        int deltaX = Math.round(event.getX() - mInitTouchLocation.x);
+        int deltaY = Math.round(event.getY() - mInitTouchLocation.y);
+        ProtoLog.v(WM_SHELL_BACK_PREVIEW, "Runner move: %d %d", deltaX, deltaY);
+        SurfaceControl topWindowLeash = mBackNavigationInfo.getDepartingWindowContainer();
+        mTransaction.setPosition(topWindowLeash, deltaX, deltaY);
+        mTouchEventDelta.set(deltaX, deltaY);
+        mTransaction.apply();
+    }
+
+    private void onGestureFinished() {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
+        if (mBackGestureStarted) {
+            if (mTriggerBack) {
+                prepareTransition();
+            } else {
+                resetPositionAnimated();
+            }
+        }
+        mBackGestureStarted = false;
+        mTriggerBack = false;
+    }
+
+    /**
+     * Animate the top window leash to its initial position.
+     */
+    private void resetPositionAnimated() {
+        mBackGestureStarted = false;
+        // TODO(208786853) Handle overlap with a new coming gesture.
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Runner: Back not triggered, cancelling animation "
+                + "mLastPos=%s mInitTouch=%s", mTouchEventDelta, mInitTouchLocation);
+
+        // TODO(208427216) : Replace placeholder animation with an actual one.
+        ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f).setDuration(200);
+        animation.addUpdateListener(animation1 -> {
+            if (mBackNavigationInfo == null) {
+                return;
+            }
+            float fraction = animation1.getAnimatedFraction();
+            int deltaX = Math.round(mTouchEventDelta.x - (mTouchEventDelta.x * fraction));
+            int deltaY = Math.round(mTouchEventDelta.y - (mTouchEventDelta.y * fraction));
+            mTransaction.setPosition(mBackNavigationInfo.getDepartingWindowContainer(),
+                    deltaX, deltaY);
+            mTransaction.apply();
+        });
+
+        animation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onAnimationEnd");
+                finishAnimation();
+            }
+        });
+        animation.start();
+    }
+
+    private void prepareTransition() {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "prepareTransition()");
+        mTriggerBack = false;
+        mBackGestureStarted = false;
+    }
+
+    /**
+     * Sets to true when the back gesture has passed the triggering threshold, false otherwise.
+     */
+    public void setTriggerBack(boolean triggerBack) {
+        mTriggerBack = triggerBack;
+    }
+
+    private void finishAnimation() {
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
+        mBackGestureStarted = false;
+        mTouchEventDelta.set(0, 0);
+        mInitTouchLocation.set(0, 0);
+        BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
+        mBackNavigationInfo = null;
+        if (backNavigationInfo == null) {
+            return;
+        }
+        SurfaceControl topWindowLeash = backNavigationInfo.getDepartingWindowContainer();
+        if (topWindowLeash != null && topWindowLeash.isValid()) {
+            mTransaction.remove(topWindowLeash);
+        }
+        SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
+        if (screenshotSurface != null && screenshotSurface.isValid()) {
+            mTransaction.remove(screenshotSurface);
+        }
+        mTransaction.apply();
+        backNavigationInfo.onBackNavigationFinished();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java
deleted file mode 100644
index dc20f7b..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java
+++ /dev/null
@@ -1,41 +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.wm.shell.back;
-
-import android.os.SystemProperties;
-import android.view.IWindowManager;
-
-import javax.inject.Inject;
-
-/**
- * Handle the preview of what a back gesture will lead to.
- */
-public class BackPreviewHandler {
-
-    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
-
-    public static boolean isEnabled() {
-        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
-    }
-
-    private final IWindowManager mWmService;
-
-    @Inject
-    public BackPreviewHandler(IWindowManager windowManagerService) {
-        mWmService = windowManagerService;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 7db49f0..e2bc360 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -34,6 +35,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This module deals with display rotations coming from WM. When WM starts a rotation: after it has
@@ -243,6 +245,19 @@
         }
     }
 
+    private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+        synchronized (mDisplays) {
+            if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) {
+                Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown"
+                        + " display, displayId=" + displayId);
+                return;
+            }
+            for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
+                mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas);
+            }
+        }
+    }
+
     private static class DisplayRecord {
         private int mDisplayId;
         private Context mContext;
@@ -301,6 +316,13 @@
                 DisplayController.this.onFixedRotationFinished(displayId);
             });
         }
+
+        @Override
+        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {
+            mMainExecutor.execute(() -> {
+                DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas);
+            });
+        }
     }
 
     /**
@@ -335,5 +357,10 @@
          * Called when fixed rotation on a display is finished.
          */
         default void onFixedRotationFinished(int displayId) {}
+
+        /**
+         * Called when keep-clear areas on a display have changed.
+         */
+        default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
     }
 }
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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 23d9b8b..f61e624 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -40,6 +40,8 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationController;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -238,6 +240,17 @@
     }
 
     //
+    // Back animation
+    //
+
+    @WMSingleton
+    @Provides
+    static Optional<BackAnimation> provideBackAnimation(
+            Optional<BackAnimationController> backAnimationController) {
+        return backAnimationController.map(BackAnimationController::getBackAnimationImpl);
+    }
+
+    //
     // Bubbles (optional feature)
     //
 
@@ -678,4 +691,16 @@
                 legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
                 hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
     }
+
+    @WMSingleton
+    @Provides
+    static Optional<BackAnimationController> provideBackAnimationController(
+            @ShellMainThread ShellExecutor shellExecutor
+    ) {
+        if (BackAnimationController.IS_ENABLED) {
+            return Optional.of(
+                    new BackAnimationController(shellExecutor));
+        }
+        return Optional.empty();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 10aa8a0..225305b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -105,8 +105,6 @@
     private static final float MENU_BACKGROUND_ALPHA = 0.3f;
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
-    private static final boolean ENABLE_ENTER_SPLIT = true;
-
     private int mMenuState;
     private boolean mAllowMenuTimeout = true;
     private boolean mAllowTouches = true;
@@ -281,6 +279,8 @@
             boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) {
         mAllowMenuTimeout = allowMenuTimeout;
         mDidLastShowMenuResize = resizeMenuOnShow;
+        final boolean enableEnterSplit =
+                mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton);
         if (mMenuState != menuState) {
             // Disallow touches if the menu needs to resize while showing, and we are transitioning
             // to/from a full menu state.
@@ -301,7 +301,7 @@
                     mDismissButton.getAlpha(), 1f);
             ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA,
                     mEnterSplitButton.getAlpha(),
-                    ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f);
+                    enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f);
             if (menuState == MENU_STATE_FULL) {
                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim,
                         enterSplitAnim);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 79c1df2..20c4e21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -34,6 +34,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_STARTING_WINDOW),
+    WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+            "ShellBackPreview"),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6921448..3e6dc82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -137,6 +137,9 @@
     private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
 
     private StageCoordinator mStageCoordinator;
+    // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
+    // outside the bounds of the roots by being reparented into a higher level fullscreen container
+    private SurfaceControl mSplitTasksContainerLayer;
 
     public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
             SyncTransactionQueue syncQueue, Context context,
@@ -378,20 +381,24 @@
     RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
         if (ENABLE_SHELL_TRANSITIONS || apps.length < 2) return null;
         // TODO(b/206487881): Integrate this with shell transition.
+        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        if (mSplitTasksContainerLayer != null) {
+            // Remove the previous layer before recreating
+            transaction.remove(mSplitTasksContainerLayer);
+        }
         final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
                 .setContainerLayer()
                 .setName("RecentsAnimationSplitTasks")
                 .setHidden(false)
                 .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
         mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
-        SurfaceControl sc = builder.build();
-        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+        mSplitTasksContainerLayer = builder.build();
 
         // Ensure that we order these in the parent in the right z-order as their previous order
         Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
         int layer = 1;
         for (RemoteAnimationTarget appTarget : apps) {
-            transaction.reparent(appTarget.leash, sc);
+            transaction.reparent(appTarget.leash, mSplitTasksContainerLayer);
             transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
                     appTarget.screenSpaceBounds.top);
             transaction.setLayer(appTarget.leash, layer++);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 8664d9b..d30d0cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -70,7 +70,8 @@
     IBinder mPendingRecent = null;
 
     private IBinder mAnimatingTransition = null;
-    private OneShotRemoteHandler mRemoteHandler = null;
+    private OneShotRemoteHandler mPendingRemoteHandler = null;
+    private OneShotRemoteHandler mActiveRemoteHandler = null;
 
     private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
 
@@ -96,10 +97,11 @@
             @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
         mFinishCallback = finishCallback;
         mAnimatingTransition = transition;
-        if (mRemoteHandler != null) {
-            mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
-                    mRemoteFinishCB);
-            mRemoteHandler = null;
+        if (mPendingRemoteHandler != null) {
+            mPendingRemoteHandler.startAnimation(transition, info, startTransaction,
+                    finishTransaction, mRemoteFinishCB);
+            mActiveRemoteHandler = mPendingRemoteHandler;
+            mPendingRemoteHandler = null;
             return;
         }
         playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
@@ -172,15 +174,14 @@
     IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
             @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
             @NonNull Transitions.TransitionHandler handler) {
-        if (remoteTransition != null) {
-            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
-            mRemoteHandler = new OneShotRemoteHandler(
-                    mTransitions.getMainExecutor(), remoteTransition);
-        }
         final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
         mPendingEnter = transition;
-        if (mRemoteHandler != null) {
-            mRemoteHandler.setTransition(transition);
+
+        if (remoteTransition != null) {
+            // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+            mPendingRemoteHandler = new OneShotRemoteHandler(
+                    mTransitions.getMainExecutor(), remoteTransition);
+            mPendingRemoteHandler.setTransition(transition);
         }
         return transition;
     }
@@ -211,9 +212,9 @@
 
         if (remoteTransition != null) {
             // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
-            mRemoteHandler = new OneShotRemoteHandler(
+            mPendingRemoteHandler = new OneShotRemoteHandler(
                     mTransitions.getMainExecutor(), remoteTransition);
-            mRemoteHandler.setTransition(transition);
+            mPendingRemoteHandler.setTransition(transition);
         }
 
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
@@ -221,6 +222,13 @@
         return transition;
     }
 
+    void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+            IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
+        if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) {
+            mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+        }
+    }
+
     void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
         if (!mAnimations.isEmpty()) return;
         mOnFinish.run();
@@ -241,11 +249,13 @@
         }
         if (mAnimatingTransition == mPendingRecent) {
             // If the wct is not null while finishing recent transition, it indicates it's not
-            // returning to home and hence needing the wct to reorder tasks.
-            final boolean toHome = wct == null;
-            mStageCoordinator.finishRecentAnimation(toHome);
+            // dismissing split and thus need to reorder split task so they can be on top again.
+            final boolean dismissSplit = wct == null;
+            mStageCoordinator.finishRecentAnimation(dismissSplit);
             mPendingRecent = null;
         }
+        mPendingRemoteHandler = null;
+        mActiveRemoteHandler = null;
         mAnimatingTransition = null;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c812050..e592101 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -166,17 +166,6 @@
     @StageType
     private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
 
-    private final Runnable mOnTransitionAnimationComplete = () -> {
-        // If still playing, let it finish.
-        if (!isSplitScreenVisible()) {
-            // Update divider state after animation so that it is still around and positioned
-            // properly for the animation itself.
-            mSplitLayout.release();
-            mSplitLayout.resetDividerPosition();
-            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-        }
-    };
-
     private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
             new SplitWindowManager.ParentContainerCallbacks() {
                 @Override
@@ -237,7 +226,7 @@
         deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
                 new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
-                mOnTransitionAnimationComplete, this);
+                this::onTransitionAnimationComplete, this);
         mDisplayController.addDisplayWindowListener(this);
         mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
         transitions.addHandler(this);
@@ -267,7 +256,7 @@
         mRootTDAOrganizer.registerListener(displayId, this);
         mSplitLayout = splitLayout;
         mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
-                mOnTransitionAnimationComplete, this);
+                this::onTransitionAnimationComplete, this);
         mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
         mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
         mLogger = logger;
@@ -1234,7 +1223,7 @@
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
         if (triggerTask == null) {
             // Still want to monitor everything while in split-screen, so return non-null.
-            return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+            return mMainStage.isActive() ? new WindowContainerTransaction() : null;
         } else if (triggerTask.displayId != mDisplayId) {
             // Skip handling task on the other display.
             return null;
@@ -1250,7 +1239,7 @@
             mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
         }
 
-        if (isSplitScreenVisible()) {
+        if (mMainStage.isActive()) {
             // Try to handle everything while in split-screen, so return a WCT even if it's empty.
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  split is active so using split"
                             + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
@@ -1296,6 +1285,13 @@
     }
 
     @Override
+    public void mergeAnimation(IBinder transition, TransitionInfo info,
+            SurfaceControl.Transaction t, IBinder mergeTarget,
+            Transitions.TransitionFinishCallback finishCallback) {
+        mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+    }
+
+    @Override
     public void onTransitionMerged(@NonNull IBinder transition) {
         // Once the pending enter transition got merged, make sure to bring divider bar visible and
         // clear the pending transition from cache to prevent mess-up the following state.
@@ -1375,6 +1371,17 @@
         return true;
     }
 
+    void onTransitionAnimationComplete() {
+        // If still playing, let it finish.
+        if (!mMainStage.isActive()) {
+            // Update divider state after animation so that it is still around and positioned
+            // properly for the animation itself.
+            mSplitLayout.release();
+            mSplitLayout.resetDividerPosition();
+            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+        }
+    }
+
     private boolean startPendingEnterAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
         // First, verify that we actually have opened apps in both splits.
@@ -1513,8 +1520,17 @@
         return true;
     }
 
-    void finishRecentAnimation(boolean toHome) {
-        if (toHome) {
+    void finishRecentAnimation(boolean dismissSplit) {
+        // Exclude the case that the split screen has been dismissed already.
+        if (!mMainStage.isActive()) {
+            // The latest split dismissing transition might be a no-op transition and thus won't
+            // callback startAnimation, update split visibility here to cover this kind of no-op
+            // transition case.
+            setSplitsVisible(false);
+            return;
+        }
+
+        if (dismissSplit) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
             mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
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/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
new file mode 100644
index 0000000..8446b37
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
@@ -0,0 +1,2 @@
+# window manager > wm shell > Split Screen
+# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index af629cc..f8d14c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -24,6 +24,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.helpers.BaseAppHelper
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -59,6 +62,12 @@
             }
         }
 
+    @Before
+    fun setup() {
+        // This test doesn't work in shell transitions because of b/205288792
+        Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled())
+    }
+
     @Presubmit
     @Test
     fun testAppIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index add11c1..c93c5ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -25,6 +25,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.helpers.BaseAppHelper
+import org.junit.Assume
+import org.junit.Before
 import org.junit.runner.RunWith
 import org.junit.Test
 import org.junit.runners.Parameterized
@@ -67,6 +70,12 @@
             }
         }
 
+    @Before
+    fun setup() {
+        // This test doesn't work in shell transitions because of b/205288792
+        Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled())
+    }
+
     @Presubmit
     @Test
     fun testAppIsAlwaysVisible() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS
new file mode 100644
index 0000000..566acc8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OWNERS
@@ -0,0 +1,2 @@
+# window manager > wm shell > Bubbles
+# Bug component: 555586
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/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
new file mode 100644
index 0000000..8446b37
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
@@ -0,0 +1,2 @@
+# window manager > wm shell > Split Screen
+# Bug component: 928697
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/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 2c08b7f..3a9a070 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -82,6 +82,11 @@
     @Test
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 197726610)
+    @Test
+    override fun pipLayerExpands() = super.pipLayerExpands()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index e340f4c..03c8929f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -101,6 +101,11 @@
     @Test
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 197726610)
+    @Test
+    override fun pipLayerExpands() = super.pipLayerExpands()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index 8adebb8..976b7c6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -90,6 +90,11 @@
     @Test
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
+    /** {@inheritDoc}  */
+    @FlakyTest(bugId = 215869110)
+    @Test
+    override fun focusDoesNotChange() = super.focusDoesNotChange()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS
new file mode 100644
index 0000000..172e24bf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/OWNERS
@@ -0,0 +1,2 @@
+# window manager > wm shell > Picture-In-Picture
+# Bug component: 316251
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
new file mode 100644
index 0000000..960c7ac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.back;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityTaskManager;
+import android.app.WindowConfiguration;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.window.BackNavigationInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * atest WMShellUnitTests:BackAnimationControllerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BackAnimationControllerTest {
+
+    private final ShellExecutor mShellExecutor = new TestShellExecutor();
+
+    @Mock
+    private SurfaceControl.Transaction mTransaction;
+
+    @Mock
+    private IActivityTaskManager mActivityTaskManager;
+
+    private BackAnimationController mController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mController = new BackAnimationController(
+                mShellExecutor, mTransaction, mActivityTaskManager);
+    }
+
+    private void createNavigationInfo(SurfaceControl topWindowLeash,
+            SurfaceControl screenshotSurface,
+            HardwareBuffer hardwareBuffer) {
+        BackNavigationInfo navigationInfo = new BackNavigationInfo(
+                BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                topWindowLeash,
+                screenshotSurface,
+                hardwareBuffer,
+                new WindowConfiguration(),
+                new RemoteCallback((bundle) -> {}));
+        try {
+            doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation();
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    @Test
+    public void screenshotAttachedAndVisible() {
+        SurfaceControl topWindowLeash = new SurfaceControl();
+        SurfaceControl screenshotSurface = new SurfaceControl();
+        HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
+        createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
+        mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+        verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
+        verify(mTransaction).setVisibility(screenshotSurface, true);
+        verify(mTransaction).apply();
+    }
+
+    @Test
+    public void surfaceMovesWithGesture() {
+        SurfaceControl topWindowLeash = new SurfaceControl();
+        SurfaceControl screenshotSurface = new SurfaceControl();
+        HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
+        createNavigationInfo(topWindowLeash, screenshotSurface, hardwareBuffer);
+        mController.onMotionEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0));
+        mController.onMotionEvent(MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0));
+        verify(mTransaction).setPosition(topWindowLeash, 100, 100);
+        verify(mTransaction, atLeastOnce()).apply();
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index fe66e22..35e4982 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
@@ -111,7 +110,6 @@
     private ActivityManager.RunningTaskInfo mHomeTask;
     private ActivityManager.RunningTaskInfo mFullscreenAppTask;
     private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
-    private ActivityManager.RunningTaskInfo mSplitPrimaryAppTask;
 
     @Before
     public void setUp() throws RemoteException {
@@ -144,8 +142,6 @@
         mNonResizeableFullscreenAppTask =
                 createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         mNonResizeableFullscreenAppTask.isResizeable = false;
-        mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
-                ACTIVITY_TYPE_STANDARD);
 
         setRunningTask(mFullscreenAppTask);
     }
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index 3eedda8..d87a3ce 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -29,40 +29,33 @@
 
 namespace android {
 
-void LocaleValue::set_language(const char* language_chars) {
+template <size_t N, class Transformer>
+static void safe_transform_copy(const char* source, char (&dest)[N], Transformer t) {
   size_t i = 0;
-  while ((*language_chars) != '\0') {
-    language[i++] = ::tolower(*language_chars);
-    language_chars++;
+  while (i < N && (*source) != '\0') {
+    dest[i++] = t(i, *source);
+    source++;
   }
+  while (i < N) {
+    dest[i++] = '\0';
+  }
+}
+
+void LocaleValue::set_language(const char* language_chars) {
+  safe_transform_copy(language_chars, language, [](size_t, char c) { return ::tolower(c); });
 }
 
 void LocaleValue::set_region(const char* region_chars) {
-  size_t i = 0;
-  while ((*region_chars) != '\0') {
-    region[i++] = ::toupper(*region_chars);
-    region_chars++;
-  }
+  safe_transform_copy(region_chars, region, [](size_t, char c) { return ::toupper(c); });
 }
 
 void LocaleValue::set_script(const char* script_chars) {
-  size_t i = 0;
-  while ((*script_chars) != '\0') {
-    if (i == 0) {
-      script[i++] = ::toupper(*script_chars);
-    } else {
-      script[i++] = ::tolower(*script_chars);
-    }
-    script_chars++;
-  }
+  safe_transform_copy(script_chars, script,
+                      [](size_t i, char c) { return i ? ::tolower(c) : ::toupper(c); });
 }
 
 void LocaleValue::set_variant(const char* variant_chars) {
-  size_t i = 0;
-  while ((*variant_chars) != '\0') {
-    variant[i++] = *variant_chars;
-    variant_chars++;
-  }
+  safe_transform_copy(variant_chars, variant, [](size_t, char c) { return c; });
 }
 
 static inline bool is_alpha(const std::string& str) {
@@ -234,6 +227,10 @@
   return static_cast<ssize_t>(iter - start_iter);
 }
 
+// Make sure the following memcpy's are properly sized.
+static_assert(sizeof(ResTable_config::localeScript) == sizeof(LocaleValue::script));
+static_assert(sizeof(ResTable_config::localeVariant) == sizeof(LocaleValue::variant));
+
 void LocaleValue::InitFromResTable(const ResTable_config& config) {
   config.unpackLanguage(language);
   config.unpackRegion(region);
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/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index cdfa02c..ab3dafe 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1868,7 +1868,7 @@
             gnssMeasurement.mSatelliteInterSignalBiasUncertaintyNanos = parcel.readDouble();
             if (gnssMeasurement.hasSatellitePvt()) {
                 ClassLoader classLoader = getClass().getClassLoader();
-                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader);
+                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader, android.location.SatellitePvt.class);
             }
             if (gnssMeasurement.hasCorrelationVectors()) {
                 CorrelationVector[] correlationVectorsArray =
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index 075ddeb..0397740 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -160,7 +160,7 @@
             new Creator<GnssMeasurementsEvent>() {
         @Override
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
-            GnssClock clock = in.readParcelable(getClass().getClassLoader());
+            GnssClock clock = in.readParcelable(getClass().getClassLoader(), android.location.GnssClock.class);
             List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
             List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
                     GnssAutomaticGainControl.CREATOR);
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index f3feb7a..6b834f3 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -112,7 +112,7 @@
         public GpsMeasurementsEvent createFromParcel(Parcel in) {
             ClassLoader classLoader = getClass().getClassLoader();
 
-            GpsClock clock = in.readParcelable(classLoader);
+            GpsClock clock = in.readParcelable(classLoader, android.location.GpsClock.class);
 
             int measurementsLength = in.readInt();
             GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength];
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index 2d5d6eb..b37fe3d 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -92,7 +92,7 @@
                 @Override
                 public GpsNavigationMessageEvent createFromParcel(Parcel in) {
                     ClassLoader classLoader = getClass().getClassLoader();
-                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader);
+                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader, android.location.GpsNavigationMessage.class);
                     return new GpsNavigationMessageEvent(navigationMessage);
                 }
 
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/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index 794a8d0..aa43cfd 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -465,9 +465,9 @@
                 public SatellitePvt createFromParcel(Parcel in) {
                     int flags = in.readInt();
                     ClassLoader classLoader = getClass().getClassLoader();
-                    PositionEcef positionEcef = in.readParcelable(classLoader);
-                    VelocityEcef velocityEcef = in.readParcelable(classLoader);
-                    ClockInfo clockInfo = in.readParcelable(classLoader);
+                    PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class);
+                    VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class);
+                    ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class);
                     double ionoDelayMeters = in.readDouble();
                     double tropoDelayMeters = in.readDouble();
 
diff --git a/media/aidl/android/media/audio/common/AudioContentType.aidl b/media/aidl/android/media/audio/common/AudioContentType.aidl
index 50ac181..f42ae2f 100644
--- a/media/aidl/android/media/audio/common/AudioContentType.aidl
+++ b/media/aidl/android/media/audio/common/AudioContentType.aidl
@@ -50,4 +50,8 @@
      * in a game. These sounds are mostly synthesized or short Foley sounds.
      */
     SONIFICATION = 4,
+    /**
+     * Content type value to use when the content type is ultrasound.
+     */
+    ULTRASOUND = 1997,
 }
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/android/media/audio/common/AudioInputFlags.aidl b/media/aidl/android/media/audio/common/AudioInputFlags.aidl
index e4b6ec2..83a5d9d 100644
--- a/media/aidl/android/media/audio/common/AudioInputFlags.aidl
+++ b/media/aidl/android/media/audio/common/AudioInputFlags.aidl
@@ -59,4 +59,8 @@
      * Input contains an encoded audio stream.
      */
     DIRECT = 7,
+    /**
+     * Input is for capturing "ultrasound" audio commands.
+     */
+    ULTRASOUND = 8,
 }
diff --git a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
index 0505036..2556b68 100644
--- a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
+++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl
@@ -26,7 +26,7 @@
  */
 @VintfStability
 @Backing(type="int")
-enum AudioOutputFlags {
+enum AudioOutputFlags{
     /**
      * Output must not be altered by the framework, it bypasses software mixers.
      */
@@ -97,4 +97,12 @@
      * tracks.
      */
     GAPLESS_OFFLOAD = 15,
+    /**
+     * Output is used for spatial audio.
+     */
+    SPATIALIZER = 16,
+    /**
+     * Output is used for transmitting ultrasound audio.
+     */
+    ULTRASOUND = 17,
 }
diff --git a/media/aidl/android/media/audio/common/AudioSource.aidl b/media/aidl/android/media/audio/common/AudioSource.aidl
index 527ee39..7779994 100644
--- a/media/aidl/android/media/audio/common/AudioSource.aidl
+++ b/media/aidl/android/media/audio/common/AudioSource.aidl
@@ -87,4 +87,8 @@
      * hotword detection. Same tuning as VOICE_RECOGNITION.
      */
     HOTWORD = 1999,
+    /** Microphone audio source for ultrasound sound if available,
+     *  behaves like DEFAULT otherwise.
+     */
+    ULTRASOUND = 2000,
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl
index 3798b82..f9ac614 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioContentType.aidl
@@ -40,4 +40,5 @@
   MUSIC = 2,
   MOVIE = 3,
   SONIFICATION = 4,
+  ULTRASOUND = 1997,
 }
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/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl
index 8a5dae0..37aa64a 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioInputFlags.aidl
@@ -43,4 +43,5 @@
   VOIP_TX = 5,
   HW_AV_SYNC = 6,
   DIRECT = 7,
+  ULTRASOUND = 8,
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
index ed16d17..4a512a8 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl
@@ -51,4 +51,6 @@
   VOIP_RX = 13,
   INCALL_MUSIC = 14,
   GAPLESS_OFFLOAD = 15,
+  SPATIALIZER = 16,
+  ULTRASOUND = 17,
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl
index d1dfe41..acf822e 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioSource.aidl
@@ -50,4 +50,5 @@
   ECHO_REFERENCE = 1997,
   FM_TUNER = 1998,
   HOTWORD = 1999,
+  ULTRASOUND = 2000,
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 85e49cc..ded9597 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -101,6 +101,13 @@
      * or short Foley sounds.
      */
     public final static int CONTENT_TYPE_SONIFICATION = 4;
+    /**
+     * @hide
+     * Content type value to use when the content type is ultrasound.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
+    public static final int CONTENT_TYPE_ULTRASOUND = 1997;
 
     /**
      * Invalid value, only ever used for an uninitialized usage value
@@ -958,6 +965,26 @@
         }
 
         /**
+         * @hide
+         * Sets the attribute describing the content type of the audio signal, such as speech,
+         * , music or ultrasound.
+         * @param contentType the content type values.
+         * @return the same Builder instance.
+         */
+        @SystemApi
+        public @NonNull Builder setInternalContentType(@AttrInternalContentType int contentType) {
+            switch (contentType) {
+                case CONTENT_TYPE_ULTRASOUND:
+                    mContentType = contentType;
+                    break;
+                default:
+                    setContentType(contentType);
+                    break;
+            }
+            return this;
+        }
+
+        /**
          * Sets the combination of flags.
          *
          * This is a bitwise OR with the existing flags.
@@ -1234,7 +1261,8 @@
         /**
          * @hide
          * Same as {@link #setCapturePreset(int)} but authorizes the use of HOTWORD,
-         * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL and ECHO_REFERENCE.
+         * REMOTE_SUBMIX, RADIO_TUNER, VOICE_DOWNLINK, VOICE_UPLINK, VOICE_CALL, ECHO_REFERENCE
+         * and ULTRASOUND
          * @param preset
          * @return the same Builder instance.
          */
@@ -1246,7 +1274,8 @@
                     || (preset == MediaRecorder.AudioSource.VOICE_DOWNLINK)
                     || (preset == MediaRecorder.AudioSource.VOICE_UPLINK)
                     || (preset == MediaRecorder.AudioSource.VOICE_CALL)
-                    || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)) {
+                    || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)
+                    || (preset == MediaRecorder.AudioSource.ULTRASOUND)) {
                 mSource = preset;
             } else {
                 setCapturePreset(preset);
@@ -1589,6 +1618,7 @@
             case CONTENT_TYPE_MUSIC: return new String("CONTENT_TYPE_MUSIC");
             case CONTENT_TYPE_MOVIE: return new String("CONTENT_TYPE_MOVIE");
             case CONTENT_TYPE_SONIFICATION: return new String("CONTENT_TYPE_SONIFICATION");
+            case CONTENT_TYPE_ULTRASOUND: return new String("CONTENT_TYPE_ULTRASOUND");
             default: return new String("unknown content type " + mContentType);
         }
     }
@@ -1823,4 +1853,16 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeContentType {}
+
+    /** @hide */
+    @IntDef({
+        CONTENT_TYPE_UNKNOWN,
+        CONTENT_TYPE_SPEECH,
+        CONTENT_TYPE_MUSIC,
+        CONTENT_TYPE_MOVIE,
+        CONTENT_TYPE_SONIFICATION,
+        CONTENT_TYPE_ULTRASOUND
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AttrInternalContentType {}
 }
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/AudioRecord.java b/media/java/android/media/AudioRecord.java
index e76bb42..5283889 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1033,11 +1033,12 @@
 
         //--------------
         // audio source
-        if ( (audioSource < MediaRecorder.AudioSource.DEFAULT) ||
-             ((audioSource > MediaRecorder.getAudioSourceMax()) &&
-              (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) &&
-              (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE) &&
-              (audioSource != MediaRecorder.AudioSource.HOTWORD)) )  {
+        if ((audioSource < MediaRecorder.AudioSource.DEFAULT)
+                || ((audioSource > MediaRecorder.getAudioSourceMax())
+                    && (audioSource != MediaRecorder.AudioSource.RADIO_TUNER)
+                    && (audioSource != MediaRecorder.AudioSource.ECHO_REFERENCE)
+                    && (audioSource != MediaRecorder.AudioSource.HOTWORD)
+                    && (audioSource != MediaRecorder.AudioSource.ULTRASOUND))) {
             throw new IllegalArgumentException("Invalid audio source " + audioSource);
         }
         mRecordSource = audioSource;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af5a3da..306479a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -249,7 +249,8 @@
             AUDIO_FORMAT_SBC,
             AUDIO_FORMAT_APTX,
             AUDIO_FORMAT_APTX_HD,
-            AUDIO_FORMAT_LDAC}
+            AUDIO_FORMAT_LDAC,
+            AUDIO_FORMAT_LC3}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioFormatNativeEnumForBtCodec {}
@@ -281,6 +282,7 @@
             case AUDIO_FORMAT_APTX: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX;
             case AUDIO_FORMAT_APTX_HD: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD;
             case AUDIO_FORMAT_LDAC: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC;
+            case AUDIO_FORMAT_LC3: return BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3;
             default:
                 Log.e(TAG, "Unknown audio format 0x" + Integer.toHexString(audioFormat)
                         + " for conversion to BT codec");
@@ -321,6 +323,8 @@
                 return AudioSystem.AUDIO_FORMAT_APTX_HD;
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
                 return AudioSystem.AUDIO_FORMAT_LDAC;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3:
+                return AudioSystem.AUDIO_FORMAT_LC3;
             default:
                 Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec)
                         + " for conversion to audio format");
@@ -421,6 +425,8 @@
                 return "AUDIO_FORMAT_LHDC_LL";
             case /* AUDIO_FORMAT_APTX_TWSP       */ 0x2A000000:
                 return "AUDIO_FORMAT_APTX_TWSP";
+            case /* AUDIO_FORMAT_LC3             */ 0x2B000000:
+                return "AUDIO_FORMAT_LC3";
 
             /* Aliases */
             case /* AUDIO_FORMAT_PCM_16_BIT        */ 0x1:
@@ -983,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;
@@ -1043,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<>();
@@ -1073,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
@@ -1256,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";
@@ -1355,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/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 1fc2cf9..6168c22 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -18,12 +18,16 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.graphics.GraphicBuffer;
 import android.graphics.ImageFormat;
 import android.graphics.ImageFormat.Format;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.DataSpace.NamedDataSpace;
 import android.hardware.HardwareBuffer;
+import android.hardware.HardwareBuffer.Usage;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.os.Handler;
@@ -95,10 +99,18 @@
     private ListenerHandler mListenerHandler;
     private long mNativeContext;
 
+    private int mWidth;
+    private int mHeight;
+    private final int mMaxImages;
+    private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
+    private @HardwareBuffer.Format int mHardwareBufferFormat;
+    private @NamedDataSpace long mDataSpace;
+    private boolean mUseLegacyImageFormat;
+    private boolean mUseSurfaceImageFormatInfo;
+
     // Field below is used by native code, do not access or modify.
     private int mWriterFormat;
 
-    private final int mMaxImages;
     // Keep track of the currently dequeued Image. This need to be thread safe as the images
     // could be closed by different threads (e.g., application thread and GC thread).
     private List<Image> mDequeuedImages = new CopyOnWriteArrayList<>();
@@ -131,7 +143,7 @@
      */
     public static @NonNull ImageWriter newInstance(@NonNull Surface surface,
             @IntRange(from = 1) int maxImages) {
-        return new ImageWriter(surface, maxImages, ImageFormat.UNKNOWN, -1 /*width*/,
+        return new ImageWriter(surface, maxImages, true, ImageFormat.UNKNOWN, -1 /*width*/,
                 -1 /*height*/);
     }
 
@@ -183,7 +195,7 @@
         if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
             throw new IllegalArgumentException("Invalid format is specified: " + format);
         }
-        return new ImageWriter(surface, maxImages, format, width, height);
+        return new ImageWriter(surface, maxImages, false, format, width, height);
     }
 
     /**
@@ -232,48 +244,49 @@
         if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
             throw new IllegalArgumentException("Invalid format is specified: " + format);
         }
-        return new ImageWriter(surface, maxImages, format, -1 /*width*/, -1 /*height*/);
+        return new ImageWriter(surface, maxImages, false, format, -1 /*width*/, -1 /*height*/);
     }
 
-    /**
-     * @hide
-     */
-    protected ImageWriter(Surface surface, int maxImages, int format, int width, int height) {
+    private void initializeImageWriter(Surface surface, int maxImages,
+            boolean useSurfaceImageFormatInfo, boolean useLegacyImageFormat, int imageFormat,
+            int hardwareBufferFormat, long dataSpace, int width, int height, long usage) {
         if (surface == null || maxImages < 1) {
             throw new IllegalArgumentException("Illegal input argument: surface " + surface
-                    + ", maxImages: " + maxImages);
+                + ", maxImages: " + maxImages);
         }
 
-        mMaxImages = maxImages;
-
+        mUseSurfaceImageFormatInfo = useSurfaceImageFormatInfo;
+        mUseLegacyImageFormat = useLegacyImageFormat;
         // Note that the underlying BufferQueue is working in synchronous mode
         // to avoid dropping any buffers.
-        mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, format, width,
-                height);
+        mNativeContext = nativeInit(new WeakReference<>(this), surface, maxImages, width, height,
+            useSurfaceImageFormatInfo, hardwareBufferFormat, dataSpace, usage);
 
-        // nativeInit internally overrides UNKNOWN format. So does surface format query after
-        // nativeInit and before getEstimatedNativeAllocBytes().
-        if (format == ImageFormat.UNKNOWN) {
-            format = SurfaceUtils.getSurfaceFormat(surface);
-        }
-        // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native
-        // allocation estimation sequence depends on the public formats values. To avoid
-        // possible errors, convert where necessary.
-        if (format == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) {
-            int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface);
-            switch (surfaceDataspace) {
-                case StreamConfigurationMap.HAL_DATASPACE_DEPTH:
-                    format = ImageFormat.DEPTH_POINT_CLOUD;
-                    break;
-                case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH:
-                    format = ImageFormat.DEPTH_JPEG;
-                    break;
-                case StreamConfigurationMap.HAL_DATASPACE_HEIF:
-                    format = ImageFormat.HEIC;
-                    break;
-                default:
-                    format = ImageFormat.JPEG;
+        if (useSurfaceImageFormatInfo) {
+            // nativeInit internally overrides UNKNOWN format. So does surface format query after
+            // nativeInit and before getEstimatedNativeAllocBytes().
+            imageFormat = SurfaceUtils.getSurfaceFormat(surface);
+            // Several public formats use the same native HAL_PIXEL_FORMAT_BLOB. The native
+            // allocation estimation sequence depends on the public formats values. To avoid
+            // possible errors, convert where necessary.
+            if (imageFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) {
+                int surfaceDataspace = SurfaceUtils.getSurfaceDataspace(surface);
+                switch (surfaceDataspace) {
+                    case StreamConfigurationMap.HAL_DATASPACE_DEPTH:
+                        imageFormat = ImageFormat.DEPTH_POINT_CLOUD;
+                        break;
+                    case StreamConfigurationMap.HAL_DATASPACE_DYNAMIC_DEPTH:
+                        imageFormat = ImageFormat.DEPTH_JPEG;
+                        break;
+                    case StreamConfigurationMap.HAL_DATASPACE_HEIF:
+                        imageFormat = ImageFormat.HEIC;
+                        break;
+                    default:
+                        imageFormat = ImageFormat.JPEG;
+                }
             }
+            mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
+            mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
         }
         // Estimate the native buffer allocation size and register it so it gets accounted for
         // during GC. Note that this doesn't include the buffers required by the buffer queue
@@ -282,12 +295,49 @@
         // complex, and 1 buffer is enough for the VM to treat the ImageWriter as being of some
         // size.
         Size surfSize = SurfaceUtils.getSurfaceSize(surface);
+        mWidth = width == -1 ? surfSize.getWidth() : width;
+        mHeight = height == -1 ? surfSize.getHeight() : height;
+
         mEstimatedNativeAllocBytes =
-                ImageUtils.getEstimatedNativeAllocBytes(surfSize.getWidth(),surfSize.getHeight(),
-                        format, /*buffer count*/ 1);
+            ImageUtils.getEstimatedNativeAllocBytes(mWidth, mHeight,
+                useLegacyImageFormat ? imageFormat : hardwareBufferFormat, /*buffer count*/ 1);
         VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
     }
 
+    private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
+            int imageFormat, int width, int height) {
+        mMaxImages = maxImages;
+        // update hal format and dataspace only if image format is overridden by producer.
+        mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
+        mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
+
+        initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true,
+                imageFormat, mHardwareBufferFormat, mDataSpace, width, height, mUsage);
+    }
+
+    private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
+            int imageFormat, int width, int height, long usage) {
+        mMaxImages = maxImages;
+        mUsage = usage;
+        mHardwareBufferFormat = PublicFormatUtils.getHalFormat(imageFormat);
+        mDataSpace = PublicFormatUtils.getHalDataspace(imageFormat);
+
+        initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, true,
+                imageFormat, mHardwareBufferFormat, mDataSpace, width, height, usage);
+    }
+
+    private ImageWriter(Surface surface, int maxImages, boolean useSurfaceImageFormatInfo,
+            int hardwareBufferFormat, long dataSpace, int width, int height, long usage) {
+        mMaxImages = maxImages;
+        mUsage = usage;
+        mHardwareBufferFormat = hardwareBufferFormat;
+        mDataSpace = dataSpace;
+        int publicFormat = PublicFormatUtils.getPublicFormat(hardwareBufferFormat, dataSpace);
+
+        initializeImageWriter(surface, maxImages, useSurfaceImageFormatInfo, false,
+                publicFormat, hardwareBufferFormat, dataSpace, width, height, usage);
+    }
+
     /**
      * <p>
      * Maximum number of Images that can be dequeued from the ImageWriter
@@ -316,6 +366,30 @@
     }
 
     /**
+     * The width of {@link Image Images}, in pixels.
+     *
+     * <p>If {@link Builder#setWidthAndHeight} is not called, the default width of the Image
+     * depends on the Surface provided by customer end-point.</p>
+     *
+     * @return the expected actual width of an Image.
+     */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * The height of {@link Image Images}, in pixels.
+     *
+     * <p>If {@link Builder#setWidthAndHeight} is not called, the default height of the Image
+     * depends on the Surface provided by customer end-point.</p>
+     *
+     * @return the expected height of an Image.
+     */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /**
      * <p>
      * Dequeue the next available input Image for the application to produce
      * data into.
@@ -490,6 +564,41 @@
     }
 
     /**
+     * Get the ImageWriter usage flag.
+     *
+     * @return The ImageWriter usage flag.
+     */
+    public @Usage long getUsage() {
+        return mUsage;
+    }
+
+    /**
+     * Get the ImageWriter hardwareBuffer format.
+     *
+     * <p>Use this function if the ImageWriter instance is created by builder pattern
+     * {@code ImageWriter.Builder} and using {@link Builder#setHardwareBufferFormat} and
+     * {@link Builder#setDataSpace}.</p>
+     *
+     * @return The ImageWriter hardwareBuffer format.
+     */
+    public @HardwareBuffer.Format int getHardwareBufferFormat() {
+        return mHardwareBufferFormat;
+    }
+
+    /**
+     * Get the ImageWriter dataspace.
+     *
+     * <p>Use this function if the ImageWriter instance is created by builder pattern
+     * {@code ImageWriter.Builder} and {@link Builder#setDataSpace}.</p>
+     *
+     * @return The ImageWriter dataspace.
+     */
+    @SuppressLint("MethodNameUnits")
+    public @NamedDataSpace long getDataSpace() {
+        return mDataSpace;
+    }
+
+    /**
      * ImageWriter callback interface, used to to asynchronously notify the
      * application of various ImageWriter events.
      */
@@ -755,6 +864,155 @@
         return true;
     }
 
+    /**
+     * Builder class for {@link ImageWriter} objects.
+     */
+    public static final class Builder {
+        private Surface mSurface;
+        private int mWidth = -1;
+        private int mHeight = -1;
+        private int mMaxImages = 1;
+        private int mImageFormat = ImageFormat.UNKNOWN;
+        private @Usage long mUsage = HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
+        private @HardwareBuffer.Format int mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+        private @NamedDataSpace long mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+        private boolean mUseSurfaceImageFormatInfo = true;
+        // set this as true temporarily now as a workaround to get correct format
+        // when using surface format by default without overriding the image format
+        // in the builder pattern
+        private boolean mUseLegacyImageFormat = true;
+
+        /**
+         * Constructs a new builder for {@link ImageWriter}.
+         *
+         * @param surface The destination Surface this writer produces Image data into.
+         */
+        public Builder(@NonNull Surface surface) {
+            mSurface = surface;
+        }
+
+        /**
+         * Set the width and height of images. Default size is dependent on the Surface that is
+         * provided by the downstream end-point.
+         *
+         * @param width The width in pixels that will be passed to the producer.
+         * @param height The height in pixels that will be passed to the producer.
+         * @return the Builder instance with customized width and height.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setWidthAndHeight(@IntRange(from = 1) int width,
+                @IntRange(from = 1) int height) {
+            mWidth = width;
+            mHeight = height;
+            return this;
+        }
+
+        /**
+         * Set the maximum number of images. Default value is 1.
+         *
+         * @param maxImages The maximum number of Images the user will want to access simultaneously
+         *                  for producing Image data.
+         * @return the Builder instance with customized usage value.
+         */
+        public @NonNull Builder setMaxImages(@IntRange(from = 1) int maxImages) {
+            mMaxImages = maxImages;
+            return this;
+        }
+
+        /**
+         * Set the image format of this ImageWriter.
+         * Default format depends on the Surface provided.
+         *
+         * @param imageFormat The format of the {@link ImageWriter}. It can be any valid specified
+         *                    by {@link ImageFormat} or {@link PixelFormat}.
+         * @return the Builder instance with customized image format.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public @NonNull Builder setImageFormat(@Format int imageFormat) {
+            if (!ImageFormat.isPublicFormat(imageFormat)
+                    && !PixelFormat.isPublicFormat(imageFormat)) {
+                throw new IllegalArgumentException(
+                        "Invalid imageFormat is specified: " + imageFormat);
+            }
+            mImageFormat = imageFormat;
+            mUseLegacyImageFormat = true;
+            mHardwareBufferFormat = HardwareBuffer.RGBA_8888;
+            mDataSpace = DataSpace.DATASPACE_UNKNOWN;
+            mUseSurfaceImageFormatInfo = false;
+            return this;
+        }
+
+        /**
+         * Set the hardwareBuffer format of this ImageWriter. The default value is
+         * {@link HardwareBuffer#RGBA_8888 HardwareBuffer.RGBA_8888}.
+         *
+         * <p>This function works together with {@link #setDataSpace} for an
+         * {@link ImageWriter} instance. Setting at least one of these two replaces
+         * {@link #setImageFormat} function.</p>
+         *
+         * @param hardwareBufferFormat The HardwareBuffer format of the image that this writer
+         *                             will produce.
+         * @return the Builder instance with customized buffer format.
+         *
+         * @see #setDataSpace
+         * @see #setImageFormat
+         */
+        public @NonNull Builder setHardwareBufferFormat(
+                @HardwareBuffer.Format int hardwareBufferFormat) {
+            mHardwareBufferFormat = hardwareBufferFormat;
+            mImageFormat = ImageFormat.UNKNOWN;
+            mUseLegacyImageFormat = false;
+            mUseSurfaceImageFormatInfo = false;
+            return this;
+        }
+
+        /**
+         * Set the dataspace of this ImageWriter.
+         * The default value is {@link DataSpace#DATASPACE_UNKNOWN}.
+         *
+         * @param dataSpace The dataspace of the image that this writer will produce.
+         * @return the builder instance with customized dataspace value.
+         *
+         * @see #setHardwareBufferFormat
+         */
+        public @NonNull Builder setDataSpace(@NamedDataSpace long dataSpace) {
+            mDataSpace = dataSpace;
+            mImageFormat = ImageFormat.UNKNOWN;
+            mUseLegacyImageFormat = false;
+            mUseSurfaceImageFormatInfo = false;
+            return this;
+        }
+
+        /**
+         * Set the usage flag of this ImageWriter.
+         * Default value is {@link HardwareBuffer#USAGE_CPU_WRITE_OFTEN}.
+         *
+         * @param usage The intended usage of the images produced by this ImageWriter.
+         * @return the Builder instance with customized usage flag.
+         *
+         * @see HardwareBuffer
+         */
+        public @NonNull Builder setUsage(@Usage long usage) {
+            mUsage = usage;
+            return this;
+        }
+
+        /**
+         * Builds a new ImageWriter object.
+         *
+         * @return The new ImageWriter object.
+         */
+        public @NonNull ImageWriter build() {
+            if (mUseLegacyImageFormat) {
+                return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo,
+                        mImageFormat, mWidth, mHeight, mUsage);
+            } else {
+                return new ImageWriter(mSurface, mMaxImages, mUseSurfaceImageFormatInfo,
+                        mHardwareBufferFormat, mDataSpace, mWidth, mHeight, mUsage);
+            }
+        }
+    }
+
     private static class WriterSurfaceImage extends android.media.Image {
         private ImageWriter mOwner;
         // This field is used by native code, do not access or modify.
@@ -774,6 +1032,13 @@
 
         public WriterSurfaceImage(ImageWriter writer) {
             mOwner = writer;
+            mWidth = writer.mWidth;
+            mHeight = writer.mHeight;
+
+            if (!writer.mUseLegacyImageFormat) {
+                mFormat = PublicFormatUtils.getPublicFormat(
+                        writer.mHardwareBufferFormat, writer.mDataSpace);
+            }
         }
 
         @Override
@@ -969,8 +1234,9 @@
     }
 
     // Native implemented ImageWriter methods.
-    private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImgs,
-            int format, int width, int height);
+    private synchronized native long nativeInit(Object weakSelf, Surface surface, int maxImages,
+            int width, int height, boolean useSurfaceImageFormatInfo, int hardwareBufferFormat,
+            long dataSpace, long usage);
 
     private synchronized native void nativeClose(long nativeCtx);
 
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 3c152fb..e75df1d 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -603,6 +603,18 @@
         public static final String FEATURE_QpBounds = "qp-bounds";
 
         /**
+         * <b>video encoder only</b>: codec supports exporting encoding statistics.
+         * Encoders with this feature can provide the App clients with the encoding statistics
+         * information about the frame.
+         * The scope of encoding statistics is controlled by
+         * {@link MediaFormat#KEY_VIDEO_ENCODING_STATISTICS_LEVEL}.
+         *
+         * @see MediaFormat#KEY_VIDEO_ENCODING_STATISTICS_LEVEL
+         */
+        @SuppressLint("AllUpper") // for consistency with other FEATURE_* constants
+        public static final String FEATURE_EncodingStatistics = "encoding-statistics";
+
+        /**
          * Query codec feature capabilities.
          * <p>
          * These features are supported to be used by the codec.  These
@@ -641,6 +653,7 @@
             new Feature(FEATURE_MultipleFrames, (1 << 1), false),
             new Feature(FEATURE_DynamicTimestamp, (1 << 2), false),
             new Feature(FEATURE_QpBounds, (1 << 3), false),
+            new Feature(FEATURE_EncodingStatistics, (1 << 4), false),
             // feature to exclude codec from REGULAR codec list
             new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true),
         };
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index 458562a..dece6bd 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -142,10 +142,10 @@
         mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIcon = in.readParcelable(null);
-        mIconUri = in.readParcelable(null);
+        mIcon = in.readParcelable(null, android.graphics.Bitmap.class);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mExtras = in.readBundle();
-        mMediaUri = in.readParcelable(null);
+        mMediaUri = in.readParcelable(null, android.net.Uri.class);
     }
 
     /**
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 4891d74..4956dbe 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1176,6 +1176,76 @@
     public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min";
 
     /**
+     * A key describing the level of encoding statistics information emitted from video encoder.
+     *
+     * The associated value is an integer.
+     */
+    public static final String KEY_VIDEO_ENCODING_STATISTICS_LEVEL =
+            "video-encoding-statistics-level";
+
+    /**
+     * Encoding Statistics Level None.
+     * Encoder generates no information about Encoding statistics.
+     */
+    public static final int VIDEO_ENCODING_STATISTICS_LEVEL_NONE = 0;
+
+    /**
+     * Encoding Statistics Level 1.
+     * Encoder generates {@link MediaFormat#KEY_PICTURE_TYPE} and
+     * {@link MediaFormat#KEY_VIDEO_QP_AVERAGE} for each frame.
+     */
+    public static final int VIDEO_ENCODING_STATISTICS_LEVEL_1 = 1;
+
+    /** @hide */
+    @IntDef({
+        VIDEO_ENCODING_STATISTICS_LEVEL_NONE,
+        VIDEO_ENCODING_STATISTICS_LEVEL_1,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VideoEncodingStatisticsLevel {}
+
+    /**
+     * A key describing the per-frame average block QP (Quantization Parameter).
+     * This is a part of a video 'Encoding Statistics' export feature.
+     * This value is emitted from video encoder for a video frame.
+     * The average value is rounded down (using floor()) to integer value.
+     *
+     * The associated value is an integer.
+     */
+    public static final String KEY_VIDEO_QP_AVERAGE = "video-qp-average";
+
+    /**
+     * A key describing the picture type of the encoded frame.
+     * This is a part of a video 'Encoding Statistics' export feature.
+     * This value is emitted from video encoder for a video frame.
+     *
+     * The associated value is an integer.
+     */
+    public static final String KEY_PICTURE_TYPE = "picture-type";
+
+    /** Picture Type is unknown. */
+    public static final int PICTURE_TYPE_UNKNOWN = 0;
+
+    /** Picture Type is I Frame. */
+    public static final int PICTURE_TYPE_I = 1;
+
+    /** Picture Type is P Frame. */
+    public static final int PICTURE_TYPE_P = 2;
+
+    /** Picture Type is B Frame. */
+    public static final int PICTURE_TYPE_B = 3;
+
+    /** @hide */
+    @IntDef({
+        PICTURE_TYPE_UNKNOWN,
+        PICTURE_TYPE_I,
+        PICTURE_TYPE_P,
+        PICTURE_TYPE_B,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PictureType {}
+
+    /**
      * A key describing the audio session ID of the AudioTrack associated
      * to a tunneled video codec.
      * The associated value is an integer.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 77c1e55b..d7857a0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -365,6 +365,7 @@
          */
         public static final int VOICE_PERFORMANCE = 10;
 
+
         /**
          * Source for an echo canceller to capture the reference signal to be cancelled.
          * <p>
@@ -408,6 +409,15 @@
         @SystemApi
         @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
         public static final int HOTWORD = 1999;
+
+        /** Microphone audio source for ultrasound sound if available, behaves like
+         *  {@link #DEFAULT} otherwise.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
+        public static final int ULTRASOUND = 2000;
+
     }
 
     /** @hide */
@@ -442,6 +452,7 @@
         AudioSource.ECHO_REFERENCE,
         AudioSource.RADIO_TUNER,
         AudioSource.HOTWORD,
+        AudioSource.ULTRASOUND,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SystemSource {}
@@ -454,20 +465,20 @@
      */
     public static boolean isSystemOnlyAudioSource(int source) {
         switch(source) {
-        case AudioSource.DEFAULT:
-        case AudioSource.MIC:
-        case AudioSource.VOICE_UPLINK:
-        case AudioSource.VOICE_DOWNLINK:
-        case AudioSource.VOICE_CALL:
-        case AudioSource.CAMCORDER:
-        case AudioSource.VOICE_RECOGNITION:
-        case AudioSource.VOICE_COMMUNICATION:
-        //case REMOTE_SUBMIX:  considered "system" as it requires system permissions
-        case AudioSource.UNPROCESSED:
-        case AudioSource.VOICE_PERFORMANCE:
-            return false;
-        default:
-            return true;
+            case AudioSource.DEFAULT:
+            case AudioSource.MIC:
+            case AudioSource.VOICE_UPLINK:
+            case AudioSource.VOICE_DOWNLINK:
+            case AudioSource.VOICE_CALL:
+            case AudioSource.CAMCORDER:
+            case AudioSource.VOICE_RECOGNITION:
+            case AudioSource.VOICE_COMMUNICATION:
+            //case REMOTE_SUBMIX:  considered "system" as it requires system permissions
+            case AudioSource.UNPROCESSED:
+            case AudioSource.VOICE_PERFORMANCE:
+                return false;
+            default:
+                return true;
         }
     }
 
@@ -491,6 +502,7 @@
             case AudioSource.ECHO_REFERENCE:
             case AudioSource.RADIO_TUNER:
             case AudioSource.HOTWORD:
+            case AudioSource.ULTRASOUND:
                 return true;
             default:
                 return false;
@@ -500,38 +512,40 @@
     /** @hide */
     public static final String toLogFriendlyAudioSource(int source) {
         switch(source) {
-        case AudioSource.DEFAULT:
-            return "DEFAULT";
-        case AudioSource.MIC:
-            return "MIC";
-        case AudioSource.VOICE_UPLINK:
-            return "VOICE_UPLINK";
-        case AudioSource.VOICE_DOWNLINK:
-            return "VOICE_DOWNLINK";
-        case AudioSource.VOICE_CALL:
-            return "VOICE_CALL";
-        case AudioSource.CAMCORDER:
-            return "CAMCORDER";
-        case AudioSource.VOICE_RECOGNITION:
-            return "VOICE_RECOGNITION";
-        case AudioSource.VOICE_COMMUNICATION:
-            return "VOICE_COMMUNICATION";
-        case AudioSource.REMOTE_SUBMIX:
-            return "REMOTE_SUBMIX";
-        case AudioSource.UNPROCESSED:
-            return "UNPROCESSED";
-        case AudioSource.ECHO_REFERENCE:
-            return "ECHO_REFERENCE";
-        case AudioSource.VOICE_PERFORMANCE:
-            return "VOICE_PERFORMANCE";
-        case AudioSource.RADIO_TUNER:
-            return "RADIO_TUNER";
-        case AudioSource.HOTWORD:
-            return "HOTWORD";
-        case AudioSource.AUDIO_SOURCE_INVALID:
-            return "AUDIO_SOURCE_INVALID";
-        default:
-            return "unknown source " + source;
+            case AudioSource.DEFAULT:
+                return "DEFAULT";
+            case AudioSource.MIC:
+                return "MIC";
+            case AudioSource.VOICE_UPLINK:
+                return "VOICE_UPLINK";
+            case AudioSource.VOICE_DOWNLINK:
+                return "VOICE_DOWNLINK";
+            case AudioSource.VOICE_CALL:
+                return "VOICE_CALL";
+            case AudioSource.CAMCORDER:
+                return "CAMCORDER";
+            case AudioSource.VOICE_RECOGNITION:
+                return "VOICE_RECOGNITION";
+            case AudioSource.VOICE_COMMUNICATION:
+                return "VOICE_COMMUNICATION";
+            case AudioSource.REMOTE_SUBMIX:
+                return "REMOTE_SUBMIX";
+            case AudioSource.UNPROCESSED:
+                return "UNPROCESSED";
+            case AudioSource.ECHO_REFERENCE:
+                return "ECHO_REFERENCE";
+            case AudioSource.VOICE_PERFORMANCE:
+                return "VOICE_PERFORMANCE";
+            case AudioSource.RADIO_TUNER:
+                return "RADIO_TUNER";
+            case AudioSource.HOTWORD:
+                return "HOTWORD";
+            case AudioSource.ULTRASOUND:
+                return "ULTRASOUND";
+            case AudioSource.AUDIO_SOURCE_INVALID:
+                return "AUDIO_SOURCE_INVALID";
+            default:
+                return "unknown source " + source;
         }
     }
 
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 9c9e83b..2427fa6 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -371,7 +371,7 @@
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
         mIsSystem = in.readBoolean();
-        mIconUri = in.readParcelable(null);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
         mClientPackageName = in.readString();
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index d3c8e0a..b03f785 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -31,6 +31,8 @@
 {
     MidiDeviceInfo[] getDevices();
 
+    MidiDeviceInfo[] getDevicesForTransport(int transport);
+
     // for device creation & removal notifications
     void registerListener(IBinder clientToken, in IMidiDeviceListener listener);
     void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener);
@@ -43,7 +45,7 @@
     // for registering built-in MIDI devices
     MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
             int numOutputPorts, in String[] inputPortNames, in String[] outputPortNames,
-            in Bundle properties, int type);
+            in Bundle properties, int type, int defaultProtocol);
 
     // for unregistering built-in MIDI devices
     void unregisterDeviceServer(in IMidiDeviceServer server);
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index dd3b6db..48c50f0 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -16,11 +16,15 @@
 
 package android.media.midi;
 
+import android.annotation.IntDef;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * This class contains information to describe a MIDI device.
  * For now we only have information that can be retrieved easily for USB devices,
@@ -54,6 +58,110 @@
     public static final int TYPE_BLUETOOTH = 3;
 
     /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use UMP to negotiate with the device with MIDI-CI.
+     * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_USE_MIDI_CI = 0;
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 64 bits.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1;
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2;
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 128 bits.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3;
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4;
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 2.0 through UMP.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_2_0 = 17;
+
+     /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 2.0 through UMP and jitter reduction timestamps.
+     * Call {@link MidiManager#getDevicesForTransport} with parameter
+     * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18;
+
+    /**
+     * Constant representing a device with an unknown default protocol.
+     * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0.
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+     * @see MidiDeviceInfo#getDefaultProtocol
+     */
+    public static final int PROTOCOL_UNKNOWN = -1;
+
+    /**
+     * @see MidiDeviceInfo#getDefaultProtocol
+     * @hide
+     */
+    @IntDef(prefix = { "PROTOCOL_" }, value = {
+            PROTOCOL_UMP_USE_MIDI_CI,
+            PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS,
+            PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS,
+            PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS,
+            PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS,
+            PROTOCOL_UMP_MIDI_2_0,
+            PROTOCOL_UMP_MIDI_2_0_AND_JRTS,
+            PROTOCOL_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Protocol {}
+
+    /**
      * Bundle key for the device's user visible name property.
      * The value for this property is of type {@link java.lang.String}.
      * Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
@@ -196,6 +304,7 @@
     private final String[] mOutputPortNames;
     private final Bundle mProperties;
     private final boolean mIsPrivate;
+    private final int mDefaultProtocol;
 
     /**
      * MidiDeviceInfo should only be instantiated by MidiService implementation
@@ -203,7 +312,7 @@
      */
     public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
             String[] inputPortNames, String[] outputPortNames, Bundle properties,
-            boolean isPrivate) {
+            boolean isPrivate, int defaultProtocol) {
         // Check num ports for out-of-range values. Typical values will be
         // between zero and three. More than 16 would be very unlikely
         // because the port index field in the USB packet is only 4 bits.
@@ -234,6 +343,7 @@
         }
         mProperties = properties;
         mIsPrivate = isPrivate;
+        mDefaultProtocol = defaultProtocol;
     }
 
     /**
@@ -312,6 +422,18 @@
         return mIsPrivate;
     }
 
+    /**
+     * Returns the default protocol. For most devices, this will be {@link #PROTOCOL_UNKNOWN}.
+     * Returning {@link #PROTOCOL_UNKNOWN} is not an error; the device just doesn't support
+     * Universal MIDI Packets by default.
+     *
+     * @return the device's default protocol.
+     */
+    @Protocol
+    public int getDefaultProtocol() {
+        return mDefaultProtocol;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o instanceof MidiDeviceInfo) {
@@ -331,11 +453,12 @@
         // This is a hack to force the mProperties Bundle to unparcel so we can
         // print all the names and values.
         mProperties.getString(PROPERTY_NAME);
-        return ("MidiDeviceInfo[mType=" + mType +
-                ",mInputPortCount=" + mInputPortCount +
-                ",mOutputPortCount=" + mOutputPortCount +
-                ",mProperties=" + mProperties +
-                ",mIsPrivate=" + mIsPrivate);
+        return ("MidiDeviceInfo[mType=" + mType
+                + ",mInputPortCount=" + mInputPortCount
+                + ",mOutputPortCount=" + mOutputPortCount
+                + ",mProperties=" + mProperties
+                + ",mIsPrivate=" + mIsPrivate
+                + ",mDefaultProtocol=" + mDefaultProtocol);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -349,10 +472,12 @@
             String[] inputPortNames = in.createStringArray();
             String[] outputPortNames = in.createStringArray();
             boolean isPrivate = (in.readInt() == 1);
+            int defaultProtocol = in.readInt();
             Bundle basicPropertiesIgnored = in.readBundle();
             Bundle properties = in.readBundle();
             return new MidiDeviceInfo(type, id, inputPortCount, outputPortCount,
-                    inputPortNames, outputPortNames, properties, isPrivate);
+                    inputPortNames, outputPortNames, properties, isPrivate,
+                    defaultProtocol);
         }
 
         public MidiDeviceInfo[] newArray(int size) {
@@ -390,6 +515,7 @@
         parcel.writeStringArray(mInputPortNames);
         parcel.writeStringArray(mOutputPortNames);
         parcel.writeInt(mIsPrivate ? 1 : 0);
+        parcel.writeInt(mDefaultProtocol);
         // "Basic" properties only contain properties of primitive types
         // and thus can be read back by native code. "Extra" properties is
         // a superset that contains all properties.
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
index b118279..aa06267 100644
--- a/media/java/android/media/midi/MidiDeviceStatus.java
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -115,7 +115,7 @@
         new Parcelable.Creator<MidiDeviceStatus>() {
         public MidiDeviceStatus createFromParcel(Parcel in) {
             ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
-            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader, android.media.midi.MidiDeviceInfo.class);
             boolean[] inputPortOpen = in.createBooleanArray();
             int[] outputPortOpenCount = in.createIntArray();
             return new MidiDeviceStatus(deviceInfo, inputPortOpen, outputPortOpenCount);
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index dee94c68..5348d4e 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,19 +16,31 @@
 
 package android.media.midi;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.SystemService;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
-import android.os.IBinder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.util.Log;
 
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+// BLE-MIDI
 
 /**
  * This class is the public application interface to the MIDI service.
@@ -39,6 +51,39 @@
     private static final String TAG = "MidiManager";
 
     /**
+     * Constant representing MIDI devices.
+     * These devices do NOT support Universal MIDI Packets by default.
+     * These support the original MIDI 1.0 byte stream.
+     * When communicating to a USB device, a raw byte stream will be padded for USB.
+     * Likewise, for a Bluetooth device, the raw bytes will be converted for Bluetooth.
+     * For virtual devices, the byte stream will be passed directly.
+     * If Universal MIDI Packets are needed, please use MIDI-CI.
+     * @see MidiManager#getDevicesForTransport
+     */
+    public static final int TRANSPORT_MIDI_BYTE_STREAM = 1;
+
+    /**
+     * Constant representing Universal MIDI devices.
+     * These devices do support Universal MIDI Packets (UMP) by default.
+     * When sending data to these devices, please send UMP.
+     * Packets should always be a multiple of 4 bytes.
+     * UMP is defined in the USB MIDI 2.0 spec. Please read the standard for more info.
+     * @see MidiManager#getDevicesForTransport
+     */
+    public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2;
+
+    /**
+     * @see MidiManager#getDevicesForTransport
+     * @hide
+     */
+    @IntDef(prefix = { "TRANSPORT_" }, value = {
+            TRANSPORT_MIDI_BYTE_STREAM,
+            TRANSPORT_UNIVERSAL_MIDI_PACKETS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Transport {}
+
+    /**
      * Intent for starting BluetoothMidiService
      * @hide
      */
@@ -68,43 +113,67 @@
     private class DeviceListener extends IMidiDeviceListener.Stub {
         private final DeviceCallback mCallback;
         private final Handler mHandler;
+        private final Executor mExecutor;
+        private final int mTransport;
 
-        public DeviceListener(DeviceCallback callback, Handler handler) {
+        DeviceListener(DeviceCallback callback, Handler handler, int transport) {
             mCallback = callback;
             mHandler = handler;
+            mExecutor = null;
+            mTransport = transport;
+        }
+
+        DeviceListener(DeviceCallback callback, Executor executor, int transport) {
+            mCallback = callback;
+            mHandler = null;
+            mExecutor = executor;
+            mTransport = transport;
         }
 
         @Override
         public void onDeviceAdded(MidiDeviceInfo device) {
-            if (mHandler != null) {
-                final MidiDeviceInfo deviceF = device;
-                mHandler.post(new Runnable() {
-                        @Override public void run() {
-                            mCallback.onDeviceAdded(deviceF);
-                        }
-                    });
-            } else {
-                mCallback.onDeviceAdded(device);
+            if (shouldInvokeCallback(device)) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() ->
+                            mCallback.onDeviceAdded(device));
+                } else if (mHandler != null) {
+                    final MidiDeviceInfo deviceF = device;
+                    mHandler.post(new Runnable() {
+                            @Override public void run() {
+                                mCallback.onDeviceAdded(deviceF);
+                            }
+                        });
+                } else {
+                    mCallback.onDeviceAdded(device);
+                }
             }
         }
 
         @Override
         public void onDeviceRemoved(MidiDeviceInfo device) {
-            if (mHandler != null) {
-                final MidiDeviceInfo deviceF = device;
-                mHandler.post(new Runnable() {
-                        @Override public void run() {
-                            mCallback.onDeviceRemoved(deviceF);
-                        }
-                    });
-            } else {
-                mCallback.onDeviceRemoved(device);
+            if (shouldInvokeCallback(device)) {
+                if (mExecutor != null) {
+                    mExecutor.execute(() ->
+                            mCallback.onDeviceRemoved(device));
+                } else if (mHandler != null) {
+                    final MidiDeviceInfo deviceF = device;
+                    mHandler.post(new Runnable() {
+                            @Override public void run() {
+                                mCallback.onDeviceRemoved(deviceF);
+                            }
+                        });
+                } else {
+                    mCallback.onDeviceRemoved(device);
+                }
             }
         }
 
         @Override
         public void onDeviceStatusChanged(MidiDeviceStatus status) {
-            if (mHandler != null) {
+            if (mExecutor != null) {
+                mExecutor.execute(() ->
+                        mCallback.onDeviceStatusChanged(status));
+            } else if (mHandler != null) {
                 final MidiDeviceStatus statusF = status;
                 mHandler.post(new Runnable() {
                         @Override public void run() {
@@ -115,6 +184,25 @@
                 mCallback.onDeviceStatusChanged(status);
             }
         }
+
+        /**
+         * Used to figure out whether callbacks should be invoked. Only invoke callbacks of
+         * the correct type.
+         *
+         * @param MidiDeviceInfo the device to check
+         * @return whether to invoke a callback
+         */
+        private boolean shouldInvokeCallback(MidiDeviceInfo device) {
+            // UMP devices have protocols that are not PROTOCOL_UNKNOWN
+            if (mTransport == TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
+                return (device.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN);
+            } else if (mTransport == TRANSPORT_MIDI_BYTE_STREAM) {
+                return (device.getDefaultProtocol() == MidiDeviceInfo.PROTOCOL_UNKNOWN);
+            } else {
+                Log.e(TAG, "Invalid transport type: " + mTransport);
+                return false;
+            }
+        }
     }
 
     /**
@@ -167,8 +255,10 @@
     }
 
     /**
-     * Registers a callback to receive notifications when MIDI devices are added and removed.
-     *
+     * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed.
+     * These are devices that do not default to Universal MIDI Packets. To register for a callback
+     * for those, call {@link #registerDeviceCallback} instead.
+
      * The {@link  DeviceCallback#onDeviceStatusChanged} method will be called immediately
      * for any devices that have open ports. This allows applications to know which input
      * ports are already in use and, therefore, unavailable.
@@ -180,9 +270,42 @@
      * @param handler The {@link android.os.Handler Handler} that will be used for delivering the
      *                device notifications. If handler is null, then the thread used for the
      *                callback is unspecified.
+     * @deprecated Use the {@link #registerDeviceCallback}
+     *             method with Executor and transport instead.
      */
+    @Deprecated
     public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
-        DeviceListener deviceListener = new DeviceListener(callback, handler);
+        DeviceListener deviceListener = new DeviceListener(callback, handler,
+                TRANSPORT_MIDI_BYTE_STREAM);
+        try {
+            mService.registerListener(mToken, deviceListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        mDeviceListeners.put(callback, deviceListener);
+    }
+
+    /**
+     * Registers a callback to receive notifications when MIDI devices are added and removed
+     * for a specific transport type.
+     *
+     * The {@link  DeviceCallback#onDeviceStatusChanged} method will be called immediately
+     * for any devices that have open ports. This allows applications to know which input
+     * ports are already in use and, therefore, unavailable.
+     *
+     * Applications should call {@link #getDevicesForTransport} before registering the callback
+     * to get a list of devices already added.
+     *
+     * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or
+     *            TRANSPORT_UNIVERSAL_MIDI_PACKETS.
+     * @param executor The {@link Executor} that will be used for delivering the
+     *                device notifications.
+     * @param callback a {@link DeviceCallback} for MIDI device notifications
+     */
+    public void registerDeviceCallback(@Transport int transport,
+            @NonNull Executor executor, @NonNull DeviceCallback callback) {
+        Objects.requireNonNull(executor);
+        DeviceListener deviceListener = new DeviceListener(callback, executor, transport);
         try {
             mService.registerListener(mToken, deviceListener);
         } catch (RemoteException e) {
@@ -208,10 +331,14 @@
     }
 
     /**
-     * Gets the list of all connected MIDI devices.
+     * Gets a list of connected MIDI devices. This returns all devices that do
+     * not default to Universal MIDI Packets. To get those instead, please call
+     * {@link #getDevicesForTransport} instead.
      *
-     * @return an array of all MIDI devices
+     * @return an array of MIDI devices
+     * @deprecated Use {@link #getDevicesForTransport} instead.
      */
+    @Deprecated
     public MidiDeviceInfo[] getDevices() {
         try {
            return mService.getDevices();
@@ -220,6 +347,28 @@
         }
     }
 
+    /**
+     * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM
+     * is used for MIDI 1.0 and is the most common.
+     * For devices with built in Universal MIDI Packet support, use
+     * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead.
+     *
+     * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or
+     *                  TRANSPORT_UNIVERSAL_MIDI_PACKETS.
+     * @return a collection of MIDI devices
+     */
+    public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) {
+        try {
+            MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport);
+            if (devices == null) {
+                return Collections.emptySet();
+            }
+            return new ArraySet<>(devices);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void sendOpenDeviceResponse(final MidiDevice device,
             final OnDeviceOpenedListener listener, Handler handler) {
         if (handler != null) {
@@ -284,9 +433,11 @@
         final OnDeviceOpenedListener listenerF = listener;
         final Handler handlerF = handler;
 
+        Log.d(TAG, "openBluetoothDevice() " + bluetoothDevice);
         IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
             @Override
             public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
+                Log.d(TAG, "onDeviceOpened() server:" + server);
                 MidiDevice device = null;
                 if (server != null) {
                     try {
@@ -308,16 +459,26 @@
         }
     }
 
+    /** @hide */ // for now
+    public void closeBluetoothDevice(@NonNull MidiDevice midiDevice) {
+        try {
+            midiDevice.close();
+        } catch (IOException ex) {
+            Log.e(TAG, "Exception closing BLE-MIDI device" + ex);
+        }
+    }
+
     /** @hide */
     public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
-            Bundle properties, int type, MidiDeviceServer.Callback callback) {
+            Bundle properties, int type, int defaultProtocol,
+            MidiDeviceServer.Callback callback) {
         try {
             MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
                     numOutputPorts, callback);
             MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
                     inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
-                    properties, type);
+                    properties, type, defaultProtocol);
             if (deviceInfo == null) {
                 Log.e(TAG, "registerVirtualDevice failed");
                 return null;
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 33c5490..67df1b2 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -405,5 +405,46 @@
 <a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>.
 </p>
 
+<h1 id=using_midi_2_0_over_usb>Using MIDI 2.0 over USB</h1>
+
+<p>An app can use MIDI 2.0 over USB starting in Android T. MIDI 2.0 packets are embedded in
+Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces,
+one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets.
+For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 USB spec.</p>
+
+<p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work
+exactly the same as before. In order to use the new UMP interface, retrieve the device with the
+following code snippet.</p>
+
+<pre class=prettyprint>
+Collection&#60;MidiDeviceInfo&#62; universalDeviceInfos = midiManager.getDevicesForTransport(
+        MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+</pre>
+
+<p>UMP Packets are always in multiple of 4 bytes. For each set of 4 bytes, they are sent in network
+order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p>
+
+<pre class=prettyprint>
+byte[] buffer = new byte[32];
+int numBytes = 0;
+int channel = 3; // MIDI channels 1-16 are encoded as 0-15.
+int group = 0;
+buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 voice message
+buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
+buffer[numBytes++] = (byte)60; // pitch is middle C
+buffer[numBytes++] = (byte)127; // max velocity
+int offset = 0;
+// post is non-blocking
+inputPort.send(buffer, offset, numBytes);
+</pre>
+
+<p>MIDI 2.0 messages can be sent through UMP after negotiating with the device. This is called
+MIDI-CI and is documented in the MIDI 2.0 spec. Some USB devices support pre-negotiated MIDI 2.0.
+For a MidiDeviceInfo, you can query the defaultProtocol.</p>
+
+<pre class=prettyprint>
+int defaultProtocol = info.getDefaultProtocol();
+</pre>
+
 </body>
 </html>
diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.java b/media/java/android/media/musicrecognition/RecognitionRequest.java
index 3298d63..b8757a3 100644
--- a/media/java/android/media/musicrecognition/RecognitionRequest.java
+++ b/media/java/android/media/musicrecognition/RecognitionRequest.java
@@ -152,8 +152,8 @@
     }
 
     private RecognitionRequest(Parcel in) {
-        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader());
-        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader());
+        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader(), android.media.AudioFormat.class);
+        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader(), android.media.AudioAttributes.class);
         mCaptureSession = in.readInt();
         mMaxAudioLengthSeconds = in.readInt();
         mIgnoreBeginningFrames = in.readInt();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1da41fb..955ae3c 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -1022,7 +1022,7 @@
             mVolumeControl = in.readInt();
             mMaxVolume = in.readInt();
             mCurrentVolume = in.readInt();
-            mAudioAttrs = in.readParcelable(null);
+            mAudioAttrs = in.readParcelable(null, android.media.AudioAttributes.class);
             mVolumeControlId = in.readString();
         }
 
diff --git a/media/java/android/media/tv/AitInfo.java b/media/java/android/media/tv/AitInfo.java
index ff4c625..8e80a62 100644
--- a/media/java/android/media/tv/AitInfo.java
+++ b/media/java/android/media/tv/AitInfo.java
@@ -17,12 +17,12 @@
 package android.media.tv;
 
 import android.annotation.NonNull;
+import android.media.tv.interactive.TvInteractiveAppInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
- * AIT info.
- * @hide
+ * AIT (Application Information Table) info.
  */
 public final class AitInfo implements Parcelable {
     static final String TAG = "AitInfo";
@@ -50,14 +50,15 @@
     /**
      * Constructs AIT info.
      */
-    public AitInfo(int type, int version) {
+    public AitInfo(@TvInteractiveAppInfo.InteractiveAppType int type, int version) {
         mType = type;
         mVersion = version;
     }
 
     /**
-     * Gets type.
+     * Gets interactive app type.
      */
+    @TvInteractiveAppInfo.InteractiveAppType
     public int getType() {
         return mType;
     }
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
index 4d49620..3ca63e3 100644
--- a/media/java/android/media/tv/DsmccResponse.java
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -17,10 +17,13 @@
 package android.media.tv;
 
 import android.annotation.NonNull;
+import android.annotation.StringDef;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -29,6 +32,27 @@
     public static final @TvInputManager.BroadcastInfoType int responseType =
             TvInputManager.BROADCAST_INFO_TYPE_DSMCC;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "BIOP_MESSAGE_TYPE_", value = {
+            BIOP_MESSAGE_TYPE_DIRECTORY,
+            BIOP_MESSAGE_TYPE_FILE,
+            BIOP_MESSAGE_TYPE_STREAM,
+            BIOP_MESSAGE_TYPE_SERVICE_GATEWAY,
+
+    })
+    public @interface BiopMessageType {}
+
+    /** Broadcast Inter-ORB Protocol (BIOP) message types */
+    /** BIOP directory message */
+    public static final String BIOP_MESSAGE_TYPE_DIRECTORY = "directory";
+    /** BIOP file message */
+    public static final String BIOP_MESSAGE_TYPE_FILE = "file";
+    /** BIOP stream message */
+    public static final String BIOP_MESSAGE_TYPE_STREAM = "stream";
+    /** BIOP service gateway message */
+    public static final String BIOP_MESSAGE_TYPE_SERVICE_GATEWAY = "service_gateway";
+
     public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR =
             new Parcelable.Creator<DsmccResponse>() {
                 @Override
@@ -43,39 +67,173 @@
                 }
             };
 
+    private final @BiopMessageType String mBiopMessageType;
     private final ParcelFileDescriptor mFileDescriptor;
-    private final boolean mIsDirectory;
-    private final List<String> mChildren;
+    private final List<String> mChildList;
+    private final int[] mEventIds;
+    private final String[] mEventNames;
 
     public static DsmccResponse createFromParcelBody(Parcel in) {
         return new DsmccResponse(in);
     }
 
+    /**
+     * Constructs a BIOP file message response.
+     */
     public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
-            ParcelFileDescriptor file, boolean isDirectory, List<String> children) {
+            @NonNull ParcelFileDescriptor file) {
         super(responseType, requestId, sequence, responseResult);
+        mBiopMessageType = BIOP_MESSAGE_TYPE_FILE;
         mFileDescriptor = file;
-        mIsDirectory = isDirectory;
-        mChildren = children;
+        mChildList = null;
+        mEventIds = null;
+        mEventNames = null;
     }
 
-    protected DsmccResponse(Parcel source) {
+    /**
+     * Constructs a BIOP service gateway or directory message response.
+     */
+    public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            boolean isServiceGateway, @NonNull List<String> childList) {
+        super(responseType, requestId, sequence, responseResult);
+        if (isServiceGateway) {
+            mBiopMessageType = BIOP_MESSAGE_TYPE_SERVICE_GATEWAY;
+        } else {
+            mBiopMessageType = BIOP_MESSAGE_TYPE_DIRECTORY;
+        }
+        mFileDescriptor = null;
+        mChildList = childList;
+        mEventIds = null;
+        mEventNames = null;
+    }
+
+    /**
+     * Constructs a BIOP stream message response.
+     *
+     * <p>The current stream message response does not support other stream messages types than
+     * stream event message type.
+     */
+    public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            @NonNull int[] eventIds, @NonNull String[] eventNames) {
+        super(responseType, requestId, sequence, responseResult);
+        mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM;
+        mFileDescriptor = null;
+        mChildList = null;
+        mEventIds = eventIds;
+        mEventNames = eventNames;
+        if (mEventIds.length != eventNames.length) {
+            throw new IllegalStateException("The size of eventIds and eventNames must be equal");
+        }
+    }
+
+    private DsmccResponse(@NonNull Parcel source) {
         super(responseType, source);
-        mFileDescriptor = source.readFileDescriptor();
-        mIsDirectory = (source.readInt() == 1);
-        mChildren = new ArrayList<>();
-        source.readStringList(mChildren);
+
+        mBiopMessageType = source.readString();
+        switch (mBiopMessageType) {
+            case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY:
+            case BIOP_MESSAGE_TYPE_DIRECTORY:
+                int childNum = source.readInt();
+                mChildList = new ArrayList<>();
+                for (int i = 0; i < childNum; i++) {
+                    mChildList.add(source.readString());
+                }
+                mFileDescriptor = null;
+                mEventIds = null;
+                mEventNames = null;
+                break;
+            case BIOP_MESSAGE_TYPE_FILE:
+                mFileDescriptor = source.readFileDescriptor();
+                mChildList = null;
+                mEventIds = null;
+                mEventNames = null;
+                break;
+            case BIOP_MESSAGE_TYPE_STREAM:
+                int eventNum = source.readInt();
+                mEventIds = new int[eventNum];
+                mEventNames = new String[eventNum];
+                for (int i = 0; i < eventNum; i++) {
+                    mEventIds[i] = source.readInt();
+                    mEventNames[i] = source.readString();
+                }
+                mChildList = null;
+                mFileDescriptor = null;
+                break;
+            default:
+                throw new IllegalStateException("unexpected BIOP message type");
+        }
     }
 
+    /** Returns the BIOP message type */
+    @NonNull
+    public @BiopMessageType String getBiopMessageType() {
+        return mBiopMessageType;
+    }
+
+    /** Returns the file descriptor for a given file message response */
+    @NonNull
     public ParcelFileDescriptor getFile() {
+        if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_FILE)) {
+            throw new IllegalStateException("Not file object");
+        }
         return mFileDescriptor;
     }
 
+    /**
+     * Returns a list of subobject names for the given service gateway or directory message
+     * response.
+     */
+    @NonNull
+    public List<String> getChildList() {
+        if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_DIRECTORY)
+                && !mBiopMessageType.equals(BIOP_MESSAGE_TYPE_SERVICE_GATEWAY)) {
+            throw new IllegalStateException("Not directory object");
+        }
+        return new ArrayList<String>(mChildList);
+    }
+
+    /** Returns all event IDs carried in a given stream message response. */
+    @NonNull
+    public int[] getStreamEventIds() {
+        if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
+            throw new IllegalStateException("Not stream event object");
+        }
+        return mEventIds;
+    }
+
+    /** Returns all event names carried in a given stream message response */
+    @NonNull
+    public String[] getStreamEventNames() {
+        if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) {
+            throw new IllegalStateException("Not stream event object");
+        }
+        return mEventNames;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        mFileDescriptor.writeToParcel(dest, flags);
-        dest.writeInt(mIsDirectory ? 1 : 0);
-        dest.writeStringList(mChildren);
+        dest.writeString(mBiopMessageType);
+        switch (mBiopMessageType) {
+            case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY:
+            case BIOP_MESSAGE_TYPE_DIRECTORY:
+                dest.writeInt(mChildList.size());
+                for (String child : mChildList) {
+                    dest.writeString(child);
+                }
+                break;
+            case BIOP_MESSAGE_TYPE_FILE:
+                dest.writeFileDescriptor(mFileDescriptor.getFileDescriptor());
+                break;
+            case BIOP_MESSAGE_TYPE_STREAM:
+                dest.writeInt(mEventIds.length);
+                for (int i = 0; i < mEventIds.length; i++) {
+                    dest.writeInt(mEventIds[i]);
+                    dest.writeString(mEventNames[i]);
+                }
+                break;
+            default:
+                throw new IllegalStateException("unexpected BIOP message type");
+        }
     }
 }
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
index fd75801..903fab5c2 100644
--- a/media/java/android/media/tv/StreamEventResponse.java
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -39,54 +39,53 @@
                 }
             };
 
-    private final String mName;
-    private final String mText;
-    private final String mData;
-    private final String mStatus;
+    private final int mEventId;
+    private final long mNpt;
+    private final byte[] mData;
 
     public static StreamEventResponse createFromParcelBody(Parcel in) {
         return new StreamEventResponse(in);
     }
 
     public StreamEventResponse(int requestId, int sequence, @ResponseResult int responseResult,
-            String name, String text, String data, String status) {
+            int eventId, long npt, @NonNull byte[] data) {
         super(responseType, requestId, sequence, responseResult);
-        mName = name;
-        mText = text;
+        mEventId = eventId;
+        mNpt = npt;
         mData = data;
-        mStatus = status;
     }
 
-    protected StreamEventResponse(Parcel source) {
+    private StreamEventResponse(@NonNull Parcel source) {
         super(responseType, source);
-        mName = source.readString();
-        mText = source.readString();
-        mData = source.readString();
-        mStatus = source.readString();
+        mEventId = source.readInt();
+        mNpt = source.readLong();
+        int dataLength = source.readInt();
+        mData = new byte[dataLength];
+        source.readByteArray(mData);
     }
 
-    public String getName() {
-        return mName;
+    /** Returns the event ID */
+    public int getEventId() {
+        return mEventId;
     }
 
-    public String getText() {
-        return mText;
+    /** Returns the NPT(Normal Play Time) value when the event occurred or will occur */
+    public long getNpt() {
+        return mNpt;
     }
 
-    public String getData() {
+    /** Returns the application specific data */
+    @NonNull
+    public byte[] getData() {
         return mData;
     }
 
-    public String getStatus() {
-        return mStatus;
-    }
-
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeString(mName);
-        dest.writeString(mText);
-        dest.writeString(mData);
-        dest.writeString(mStatus);
+        dest.writeInt(mEventId);
+        dest.writeLong(mNpt);
+        dest.writeInt(mData.length);
+        dest.writeByteArray(mData);
     }
 }
diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java
index f44ded3..947b2d6 100644
--- a/media/java/android/media/tv/TvContentRatingSystemInfo.java
+++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java
@@ -94,8 +94,8 @@
     };
 
     private TvContentRatingSystemInfo(Parcel in) {
-        mXmlUri = in.readParcelable(null);
-        mApplicationInfo = in.readParcelable(null);
+        mXmlUri = in.readParcelable(null, android.net.Uri.class);
+        mApplicationInfo = in.readParcelable(null, android.content.pm.ApplicationInfo.class);
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 54cb2bf..e60d537 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -653,16 +653,16 @@
         mType = in.readInt();
         mIsHardwareInput = in.readByte() == 1;
         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIconUri = in.readParcelable(null);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mLabelResId = in.readInt();
-        mIcon = in.readParcelable(null);
-        mIconStandby = in.readParcelable(null);
-        mIconDisconnected = in.readParcelable(null);
+        mIcon = in.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIconStandby = in.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIconDisconnected = in.readParcelable(null, android.graphics.drawable.Icon.class);
         mSetupActivity = in.readString();
         mCanRecord = in.readByte() == 1;
         mCanPauseRecording = in.readByte() == 1;
         mTunerCount = in.readInt();
-        mHdmiDeviceInfo = in.readParcelable(null);
+        mHdmiDeviceInfo = in.readParcelable(null, android.hardware.hdmi.HdmiDeviceInfo.class);
         mIsConnectedToHdmiSwitch = in.readByte() == 1;
         mHdmiConnectionRelativePosition = in.readInt();
         mParentId = in.readString();
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98d1599..f438d29 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -31,7 +31,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat.Encoding;
 import android.media.PlaybackParams;
-import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.TvInteractiveAppManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -2318,7 +2318,7 @@
         // @GuardedBy("mMetadataLock")
         private int mVideoHeight;
 
-        private TvIAppManager.Session mIAppSession;
+        private TvInteractiveAppManager.Session mIAppSession;
         private boolean mIAppNotificationEnabled = false;
 
         private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
@@ -2331,11 +2331,11 @@
             mSessionCallbackRecordMap = sessionCallbackRecordMap;
         }
 
-        public TvIAppManager.Session getInteractiveAppSession() {
+        public TvInteractiveAppManager.Session getInteractiveAppSession() {
             return mIAppSession;
         }
 
-        public void setInteractiveAppSession(TvIAppManager.Session iAppSession) {
+        public void setInteractiveAppSession(TvInteractiveAppManager.Session iAppSession) {
             this.mIAppSession = iAppSession;
         }
 
@@ -2593,9 +2593,9 @@
 
         /**
          * Enables interactive app notification.
+         *
          * @param enabled {@code true} if you want to enable interactive app notifications.
          *                {@code false} otherwise.
-         * @hide
          */
         public void setInteractiveAppNotificationEnabled(boolean enabled) {
             if (mToken == null) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 524ba34..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
@@ -945,8 +949,13 @@
         }
 
         /**
-         * Notifies AIT info updated.
-         * @hide
+         * Informs the app that the AIT (Application Information Table) is updated.
+         *
+         * <p>This method should also be called when
+         * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
+         * info.
+         *
+         * @see #onSetInteractiveAppNotificationEnabled(boolean)
          */
         public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1198,8 +1207,16 @@
 
         /**
          * Enables or disables interactive app notification.
+         *
+         * <p>This method enables or disables the event detection from the corresponding TV input.
+         * When it's enabled, the TV input service detects events related to interactive app, such
+         * as AIT (Application Information Table) and sends to TvView or the linked TV interactive
+         * app service.
+         *
          * @param enabled {@code true} to enable, {@code false} to disable.
-         * @hide
+         *
+         * @see TvView#setInteractiveAppNotificationEnabled(boolean)
+         * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
          */
         public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
         }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 71f6ad6..6c25a70 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -481,10 +481,18 @@
     }
 
     /**
-     * Enables interactive app notification.
+     * Enables or disables interactive app notification.
+     *
+     * <p>This method enables or disables the event detection from the corresponding TV input. When
+     * it's enabled, the TV input service detects events related to interactive app, such as
+     * AIT (Application Information Table) and sends to TvView or the linked TV interactive app
+     * service.
+     *
      * @param enabled {@code true} if you want to enable interactive app notifications.
      *                {@code false} otherwise.
-     * @hide
+     *
+     * @see TvInputService.Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
+     * @see android.media.tv.interactive.TvInteractiveAppView#setTvView(TvView)
      */
     public void setInteractiveAppNotificationEnabled(boolean enabled) {
         if (mSession != null) {
@@ -1062,12 +1070,11 @@
         }
 
         /**
-         * This is called when the AIT info has been updated.
+         * This is called when the AIT (Application Information Table) info has been updated.
          *
          * @param aitInfo The current AIT info.
-         * @hide
          */
-        public void onAitInfoUpdated(String inputId, AitInfo aitInfo) {
+        public void onAitInfoUpdated(@NonNull String inputId, @NonNull AitInfo aitInfo) {
         }
 
         /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
copy to media/java/android/media/tv/interactive/AppLinkInfo.aidl
index 2b3e961..7c52d01 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISmartspaceTransitionController.aidl
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.aidl
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system.smartspace;
+package android.media.tv.interactive;
 
-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
+parcelable AppLinkInfo;
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java
new file mode 100644
index 0000000..5cce443
--- /dev/null
+++ b/media/java/android/media/tv/interactive/AppLinkInfo.java
@@ -0,0 +1,234 @@
+/*
+ * 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.media.tv.interactive;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * App link information used by TV interactive app to launch Android apps.
+ * @hide
+ */
+public final class AppLinkInfo implements Parcelable {
+    private @NonNull String mPackageName;
+    private @NonNull String mClassName;
+    private @Nullable String mUriScheme;
+    private @Nullable String mUriHost;
+    private @Nullable String mUriPrefix;
+
+
+    /**
+     * Creates a new AppLinkInfo.
+     */
+    private AppLinkInfo(
+            @NonNull String packageName,
+            @NonNull String className,
+            @Nullable String uriScheme,
+            @Nullable String uriHost,
+            @Nullable String uriPrefix) {
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mClassName = className;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mClassName);
+        this.mUriScheme = uriScheme;
+        this.mUriHost = uriHost;
+        this.mUriPrefix = uriPrefix;
+    }
+
+    /**
+     * Gets package name of the App link.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Gets package class of the App link.
+     */
+    @NonNull
+    public String getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * Gets URI scheme of the App link.
+     */
+    @Nullable
+    public String getUriScheme() {
+        return mUriScheme;
+    }
+
+    /**
+     * Gets URI host of the App link.
+     */
+    @Nullable
+    public String getUriHost() {
+        return mUriHost;
+    }
+
+    /**
+     * Gets URI prefix of the App link.
+     */
+    @Nullable
+    public String getUriPrefix() {
+        return mUriPrefix;
+    }
+
+    @Override
+    public String toString() {
+        return "AppLinkInfo { "
+                + "packageName = " + mPackageName + ", "
+                + "className = " + mClassName + ", "
+                + "uriScheme = " + mUriScheme + ", "
+                + "uriHost = " + mUriHost + ", "
+                + "uriPrefix = " + mUriPrefix
+                + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPackageName);
+        dest.writeString(mClassName);
+        dest.writeString(mUriScheme);
+        dest.writeString(mUriHost);
+        dest.writeString(mUriPrefix);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /* package-private */ AppLinkInfo(@NonNull Parcel in) {
+        String packageName = in.readString();
+        String className = in.readString();
+        String uriScheme = in.readString();
+        String uriHost = in.readString();
+        String uriPrefix = in.readString();
+
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mClassName = className;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mClassName);
+        this.mUriScheme = uriScheme;
+        this.mUriHost = uriHost;
+        this.mUriPrefix = uriPrefix;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<AppLinkInfo> CREATOR =
+            new Parcelable.Creator<AppLinkInfo>() {
+                @Override
+                public AppLinkInfo[] newArray(int size) {
+                    return new AppLinkInfo[size];
+                }
+
+                @Override
+                public AppLinkInfo createFromParcel(@NonNull Parcel in) {
+                    return new AppLinkInfo(in);
+                }
+            };
+
+    /**
+     * A builder for {@link AppLinkInfo}
+     */
+    public static final class Builder {
+        private @NonNull String mPackageName;
+        private @NonNull String mClassName;
+        private @Nullable String mUriScheme;
+        private @Nullable String mUriHost;
+        private @Nullable String mUriPrefix;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder(
+                @NonNull String packageName,
+                @NonNull String className) {
+            mPackageName = packageName;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mPackageName);
+            mClassName = className;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mClassName);
+        }
+
+        /**
+         * Sets package name of the App link.
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String value) {
+            mPackageName = value;
+            return this;
+        }
+
+        /**
+         * Sets app name of the App link.
+         */
+        @NonNull
+        public Builder setClassName(@NonNull String value) {
+            mClassName = value;
+            return this;
+        }
+
+        /**
+         * Sets URI scheme of the App link.
+         */
+        @NonNull
+        public Builder setUriScheme(@Nullable String value) {
+            mUriScheme = value;
+            return this;
+        }
+
+        /**
+         * Sets URI host of the App link.
+         */
+        @NonNull
+        public Builder setUriHost(@Nullable String value) {
+            mUriHost = value;
+            return this;
+        }
+
+        /**
+         * Sets URI prefix of the App link.
+         */
+        @NonNull
+        public Builder setUriPrefix(@Nullable String value) {
+            mUriPrefix = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        @NonNull
+        public AppLinkInfo build() {
+            AppLinkInfo o = new AppLinkInfo(
+                    mPackageName,
+                    mClassName,
+                    mUriScheme,
+                    mUriHost,
+                    mUriPrefix);
+            return o;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 1a8fc46..a3e58d1 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -34,7 +34,7 @@
     void onLayoutSurface(int left, int top, int right, int bottom, int seq);
     void onBroadcastInfoRequest(in BroadcastInfoRequest request, int seq);
     void onRemoveBroadcastInfo(int id, int seq);
-    void onSessionStateChanged(int state, int seq);
+    void onSessionStateChanged(int state, int err, int seq);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq);
     void onTeletextAppStateChanged(int state, int seq);
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
similarity index 93%
rename from media/java/android/media/tv/interactive/ITvIAppManager.aidl
rename to media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index a19a2d2..aaabe34 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -20,6 +20,7 @@
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
+import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
 import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
 import android.media.tv.interactive.TvInteractiveAppInfo;
@@ -31,11 +32,11 @@
  * Interface to the TV interactive app service.
  * @hide
  */
-interface ITvIAppManager {
+interface ITvInteractiveAppManager {
     List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId);
     void prepare(String tiasId, int type, int userId);
-    void registerAppLinkInfo(String tiasId, in Bundle info, int userId);
-    void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId);
+    void registerAppLinkInfo(String tiasId, in AppLinkInfo info, int userId);
+    void unregisterAppLinkInfo(String tiasId, in AppLinkInfo info, int userId);
     void sendAppLinkCommand(String tiasId, in Bundle command, int userId);
     void startInteractiveApp(in IBinder sessionToken, int userId);
     void stopInteractiveApp(in IBinder sessionToken, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
index f4510f6..23be4c6 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManagerCallback.aidl
@@ -27,5 +27,5 @@
     void onInteractiveAppServiceRemoved(in String iAppServiceId);
     void onInteractiveAppServiceUpdated(in String iAppServiceId);
     void onTvInteractiveAppInfoUpdated(in TvInteractiveAppInfo tvIAppInfo);
-    void onStateChanged(in String iAppServiceId, int type, int state);
+    void onStateChanged(in String iAppServiceId, int type, int state, int err);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
index c1e6622..b6d518f 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
 import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
 import android.os.Bundle;
@@ -23,7 +24,7 @@
 
 /**
  * Top-level interface to a TV Interactive App component (implemented in a Service). It's used for
- * TvIAppManagerService to communicate with TvIAppService.
+ * TvInteractiveAppManagerService to communicate with TvInteractiveAppService.
  * @hide
  */
 oneway interface ITvInteractiveAppService {
@@ -32,7 +33,7 @@
     void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback,
             in String iAppServiceId, int type);
     void prepare(int type);
-    void registerAppLinkInfo(in Bundle info);
-    void unregisterAppLinkInfo(in Bundle info);
+    void registerAppLinkInfo(in AppLinkInfo info);
+    void unregisterAppLinkInfo(in AppLinkInfo info);
     void sendAppLinkCommand(in Bundle command);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
index f56d3bd..970b943 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppServiceCallback.aidl
@@ -17,10 +17,10 @@
 package android.media.tv.interactive;
 
 /**
- * Helper interface for ITvInteractiveAppService to allow the TvIAppService to notify the
- * TvIAppManagerService.
+ * Helper interface for ITvInteractiveAppService to allow the TvInteractiveAppService to notify the
+ * TvInteractiveAppManagerService.
  * @hide
  */
 oneway interface ITvInteractiveAppServiceCallback {
-    void onStateChanged(int type, int state);
+    void onStateChanged(int type, int state, int error);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index c270424..385f0d4 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -24,7 +24,7 @@
 import android.os.Bundle;
 
 /**
- * Helper interface for ITvInteractiveAppSession to allow TvIAppService to notify the
+ * Helper interface for ITvInteractiveAppSession to allow TvInteractiveAppService to notify the
  * system service when there is a related event.
  * @hide
  */
@@ -33,7 +33,7 @@
     void onLayoutSurface(int left, int top, int right, int bottom);
     void onBroadcastInfoRequest(in BroadcastInfoRequest request);
     void onRemoveBroadcastInfo(int id);
-    void onSessionStateChanged(int state);
+    void onSessionStateChanged(int state, int err);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId);
     void onTeletextAppStateChanged(int state);
     void onCommandRequest(in String cmdType, in Bundle parameters);
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
index 2f96552..e1f535c 100644
--- a/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppInfo.java
@@ -44,7 +44,6 @@
 
 /**
  * This class is used to specify meta information of a TV interactive app.
- * @hide
  */
 public final class TvInteractiveAppInfo implements Parcelable {
     private static final boolean DEBUG = false;
@@ -59,7 +58,7 @@
             INTERACTIVE_APP_TYPE_ATSC,
             INTERACTIVE_APP_TYPE_GINGA,
     })
-    @interface InteractiveAppType {}
+    public @interface InteractiveAppType {}
 
     /** HbbTV interactive app type */
     public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1;
@@ -77,7 +76,7 @@
             throw new IllegalArgumentException("context cannot be null.");
         }
         Intent intent =
-                new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+                new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component);
         ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
         if (resolveInfo == null) {
@@ -171,10 +170,10 @@
         ServiceInfo si = resolveInfo.serviceInfo;
         PackageManager pm = context.getPackageManager();
         try (XmlResourceParser parser =
-                     si.loadXmlMetaData(pm, TvIAppService.SERVICE_META_DATA)) {
+                     si.loadXmlMetaData(pm, TvInteractiveAppService.SERVICE_META_DATA)) {
             if (parser == null) {
                 throw new IllegalStateException(
-                        "No " + TvIAppService.SERVICE_META_DATA
+                        "No " + TvInteractiveAppService.SERVICE_META_DATA
                         + " meta-data found for " + si.name);
             }
 
@@ -194,9 +193,9 @@
             }
 
             TypedArray sa = res.obtainAttributes(attrs,
-                    com.android.internal.R.styleable.TvIAppService);
+                    com.android.internal.R.styleable.TvInteractiveAppService);
             CharSequence[] textArr = sa.getTextArray(
-                    com.android.internal.R.styleable.TvIAppService_supportedTypes);
+                    com.android.internal.R.styleable.TvInteractiveAppService_supportedTypes);
             for (CharSequence cs : textArr) {
                 types.add(cs.toString().toLowerCase());
             }
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
similarity index 86%
rename from media/java/android/media/tv/interactive/TvIAppManager.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index f819438..c5dfaa2 100755
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,45 +53,116 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * 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_IAPP_SERVICE)
-public final class TvIAppManager {
+@SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
+public final class TvInteractiveAppManager {
     // TODO: cleanup and unhide public APIs
-    private static final String TAG = "TvIAppManager";
+    private static final String TAG = "TvInteractiveAppManager";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = false, prefix = "TV_INTERACTIVE_APP_RTE_STATE_", value = {
-            TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED,
-            TV_INTERACTIVE_APP_RTE_STATE_PREPARING,
-            TV_INTERACTIVE_APP_RTE_STATE_READY,
-            TV_INTERACTIVE_APP_RTE_STATE_ERROR})
-    public @interface TvInteractiveAppRteState {}
+    @IntDef(flag = false, prefix = "SERVICE_STATE_", value = {
+            SERVICE_STATE_UNREALIZED,
+            SERVICE_STATE_PREPARING,
+            SERVICE_STATE_READY,
+            SERVICE_STATE_ERROR})
+    public @interface ServiceState {}
 
     /**
-     * Unrealized state of interactive app RTE.
-     * @hide
+     * Unrealized state of interactive app service.
      */
-    public static final int TV_INTERACTIVE_APP_RTE_STATE_UNREALIZED = 1;
+    public static final int SERVICE_STATE_UNREALIZED = 1;
     /**
-     * Preparing state of interactive app RTE.
-     * @hide
+     * Preparing state of interactive app service.
      */
-    public static final int TV_INTERACTIVE_APP_RTE_STATE_PREPARING = 2;
+    public static final int SERVICE_STATE_PREPARING = 2;
     /**
-     * Ready state of interactive app RTE.
-     * @hide
+     * Ready state of interactive app service.
+     *
+     * <p>In this state, the interactive app service is ready, and interactive apps can be started.
+     *
+     * @see TvInteractiveAppView#startInteractiveApp()
      */
-    public static final int TV_INTERACTIVE_APP_RTE_STATE_READY = 3;
+    public static final int SERVICE_STATE_READY = 3;
     /**
-     * Error state of interactive app RTE.
-     * @hide
+     * Error state of interactive app service.
      */
-    public static final int TV_INTERACTIVE_APP_RTE_STATE_ERROR = 4;
+    public static final int SERVICE_STATE_ERROR = 4;
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "INTERACTIVE_APP_STATE_", value = {
+            INTERACTIVE_APP_STATE_STOPPED,
+            INTERACTIVE_APP_STATE_RUNNING,
+            INTERACTIVE_APP_STATE_ERROR})
+    public @interface InteractiveAppState {}
+
+    /**
+     * Stopped (or not started) state of interactive application.
+     */
+    public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
+    /**
+     * Running state of interactive application.
+     */
+    public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
+    /**
+     * Error state of interactive application.
+     */
+    public static final int INTERACTIVE_APP_STATE_ERROR = 3;
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "ERROR_", value = {
+            ERROR_NONE,
+            ERROR_UNKNOWN,
+            ERROR_NOT_SUPPORTED,
+            ERROR_WEAK_SIGNAL,
+            ERROR_RESOURCE_UNAVAILABLE,
+            ERROR_BLOCKED,
+            ERROR_ENCRYPTED,
+            ERROR_UNKNOWN_CHANNEL,
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * No error.
+     */
+    public static final int ERROR_NONE = 0;
+    /**
+     * Unknown error code.
+     */
+    public static final int ERROR_UNKNOWN = 1;
+    /**
+     * Error code for an unsupported channel.
+     */
+    public static final int ERROR_NOT_SUPPORTED = 2;
+    /**
+     * Error code for weak signal.
+     */
+    public static final int ERROR_WEAK_SIGNAL = 3;
+    /**
+     * Error code when resource (e.g. tuner) is unavailable.
+     */
+    public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
+    /**
+     * Error code for blocked contents.
+     */
+    public static final int ERROR_BLOCKED = 5;
+    /**
+     * Error code when the key or module is missing for the encrypted channel.
+     */
+    public static final int ERROR_ENCRYPTED = 6;
+    /**
+     * Error code when the current channel is an unknown channel.
+     */
+    public static final int ERROR_UNKNOWN_CHANNEL = 7;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -190,7 +262,7 @@
      */
     public static final String KEY_BACK_URI = "back_uri";
 
-    private final ITvIAppManager mService;
+    private final ITvInteractiveAppManager mService;
     private final int mUserId;
 
     // A mapping from the sequence number of a session to its SessionCallbackRecord.
@@ -209,7 +281,7 @@
     private final ITvInteractiveAppClient mClient;
 
     /** @hide */
-    public TvIAppManager(ITvIAppManager service, int userId) {
+    public TvInteractiveAppManager(ITvInteractiveAppManager service, int userId) {
         mService = service;
         mUserId = userId;
         mClient = new ITvInteractiveAppClient.Stub() {
@@ -285,7 +357,7 @@
 
             @Override
             public void onCommandRequest(
-                    @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                    @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                     Bundle parameters,
                     int seq) {
                 synchronized (mSessionCallbackRecordMap) {
@@ -383,14 +455,14 @@
             }
 
             @Override
-            public void onSessionStateChanged(int state, int seq) {
+            public void onSessionStateChanged(int state, int err, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
                     if (record == null) {
                         Log.e(TAG, "Callback not found for seq " + seq);
                         return;
                     }
-                    record.postSessionStateChanged(state);
+                    record.postSessionStateChanged(state, err);
                 }
             }
 
@@ -458,10 +530,10 @@
             }
 
             @Override
-            public void onStateChanged(String iAppServiceId, int type, int state) {
+            public void onStateChanged(String iAppServiceId, int type, int state, int err) {
                 synchronized (mLock) {
                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
-                        record.postStateChanged(iAppServiceId, type, state);
+                        record.postStateChanged(iAppServiceId, type, state, err);
                     }
                 }
             }
@@ -477,16 +549,16 @@
 
     /**
      * Callback used to monitor status of the TV Interactive App.
-     * @hide
      */
     public abstract static class TvInteractiveAppCallback {
         /**
          * This is called when a TV Interactive App service is added to the system.
          *
          * <p>Normally it happens when the user installs a new TV Interactive App service package
-         * that implements {@link TvIAppService} interface.
+         * that implements {@link TvInteractiveAppService} interface.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
+         * @hide
          */
         public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
         }
@@ -498,6 +570,7 @@
          * App service package.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
+         * @hide
          */
         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
         }
@@ -509,6 +582,7 @@
          * re-installed or a newer version of the package exists becomes available/unavailable.
          *
          * @param iAppServiceId The ID of the TV Interactive App service.
+         * @hide
          */
         public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
         }
@@ -524,26 +598,34 @@
          *
          * @param iAppInfo The <code>TvInteractiveAppInfo</code> object that contains new
          *                 information.
+         * @hide
          */
         public void onTvInteractiveAppInfoUpdated(@NonNull TvInteractiveAppInfo iAppInfo) {
         }
 
         /**
          * This is called when the state of the interactive app service is changed.
-         * @hide
+         *
+         * @param type the interactive app type
+         * @param state the current state of the service of the given type
+         * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is
+         *            not {@link #SERVICE_STATE_ERROR}.
          */
         public void onTvInteractiveAppServiceStateChanged(
-                @NonNull String iAppServiceId, int type, @TvInteractiveAppRteState int state) {
+                @NonNull String iAppServiceId,
+                @TvInteractiveAppInfo.InteractiveAppType int type,
+                @ServiceState int state,
+                @ErrorCode int err) {
         }
     }
 
     private static final class TvInteractiveAppCallbackRecord {
         private final TvInteractiveAppCallback mCallback;
-        private final Handler mHandler;
+        private final Executor mExecutor;
 
-        TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Handler handler) {
+        TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) {
             mCallback = callback;
-            mHandler = handler;
+            mExecutor = executor;
         }
 
         public TvInteractiveAppCallback getCallback() {
@@ -551,7 +633,7 @@
         }
 
         public void postInteractiveAppServiceAdded(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceAdded(iAppServiceId);
@@ -560,7 +642,7 @@
         }
 
         public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
@@ -569,7 +651,7 @@
         }
 
         public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
@@ -578,7 +660,7 @@
         }
 
         public void postTvInteractiveAppInfoUpdated(final TvInteractiveAppInfo iAppInfo) {
-            mHandler.post(new Runnable() {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onTvInteractiveAppInfoUpdated(iAppInfo);
@@ -586,11 +668,12 @@
             });
         }
 
-        public void postStateChanged(String iAppServiceId, int type, int state) {
-            mHandler.post(new Runnable() {
+        public void postStateChanged(String iAppServiceId, int type, int state, int err) {
+            mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
-                    mCallback.onTvInteractiveAppServiceStateChanged(iAppServiceId, type, state);
+                    mCallback.onTvInteractiveAppServiceStateChanged(
+                            iAppServiceId, type, state, err);
                 }
             });
         }
@@ -635,7 +718,6 @@
      *
      * @return List of {@link TvInteractiveAppInfo} for each TV Interactive App service that
      *         describes its meta information.
-     * @hide
      */
     @NonNull
     public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList() {
@@ -662,7 +744,8 @@
      * Registers app link info.
      * @hide
      */
-    public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+    public void registerAppLinkInfo(
+            @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
         try {
             mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
         } catch (RemoteException e) {
@@ -675,7 +758,7 @@
      * @hide
      */
     public void unregisterAppLinkInfo(
-            @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) {
+            @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
         try {
             mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
         } catch (RemoteException e) {
@@ -699,15 +782,15 @@
      * Registers a {@link TvInteractiveAppCallback}.
      *
      * @param callback A callback used to monitor status of the TV Interactive App services.
-     * @param handler A {@link Handler} that the status change will be delivered to.
-     * @hide
+     * @param executor A {@link Executor} that the status change will be delivered to.
      */
     public void registerCallback(
-            @NonNull TvInteractiveAppCallback callback, @NonNull Handler handler) {
+            @NonNull TvInteractiveAppCallback callback,
+            @CallbackExecutor @NonNull Executor executor) {
         Preconditions.checkNotNull(callback);
-        Preconditions.checkNotNull(handler);
+        Preconditions.checkNotNull(executor);
         synchronized (mLock) {
-            mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, handler));
+            mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor));
         }
     }
 
@@ -715,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);
@@ -742,7 +824,7 @@
 
         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
 
-        private final ITvIAppManager mService;
+        private final ITvInteractiveAppManager mService;
         private final int mUserId;
         private final int mSeq;
         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
@@ -759,7 +841,7 @@
         private TvInputEventSender mSender;
         private InputChannel mInputChannel;
 
-        private Session(IBinder token, InputChannel channel, ITvIAppManager service,
+        private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service,
                 int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
             mToken = token;
             mInputChannel = channel;
@@ -1142,7 +1224,7 @@
         }
 
         /**
-         * Notifies IAPP session when video is available.
+         * Notifies Interactive APP session when video is available.
          */
         public void notifyVideoAvailable() {
             if (mToken == null) {
@@ -1157,7 +1239,7 @@
         }
 
         /**
-         * Notifies IAPP session when video is unavailable.
+         * Notifies Interactive APP session when video is unavailable.
          */
         public void notifyVideoUnavailable(int reason) {
             if (mToken == null) {
@@ -1172,7 +1254,7 @@
         }
 
         /**
-         * Notifies IAPP session when content is allowed.
+         * Notifies Interactive APP session when content is allowed.
          */
         public void notifyContentAllowed() {
             if (mToken == null) {
@@ -1187,7 +1269,7 @@
         }
 
         /**
-         * Notifies IAPP session when content is blocked.
+         * Notifies Interactive APP session when content is blocked.
          */
         public void notifyContentBlocked(TvContentRating rating) {
             if (mToken == null) {
@@ -1478,7 +1560,7 @@
         }
 
         void postCommandRequest(
-                final @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                final @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 final Bundle parameters) {
             mHandler.post(new Runnable() {
                 @Override
@@ -1553,11 +1635,11 @@
             });
         }
 
-        void postSessionStateChanged(int state) {
+        void postSessionStateChanged(int state, int err) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    mSessionCallback.onSessionStateChanged(mSession, state);
+                    mSessionCallback.onSessionStateChanged(mSession, state, err);
                 }
             });
         }
@@ -1587,28 +1669,28 @@
      */
     public abstract static class SessionCallback {
         /**
-         * This is called after {@link TvIAppManager#createSession} has been processed.
+         * This is called after {@link TvInteractiveAppManager#createSession} has been processed.
          *
-         * @param session A {@link TvIAppManager.Session} instance created. This can be
+         * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be
          *                {@code null} if the creation request failed.
          */
         public void onSessionCreated(@Nullable Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppManager.Session} is released.
+         * This is called when {@link TvInteractiveAppManager.Session} is released.
          * This typically happens when the process hosting the session has crashed or been killed.
          *
-         * @param session the {@link TvIAppManager.Session} instance released.
+         * @param session the {@link TvInteractiveAppManager.Session} instance released.
          */
         public void onSessionReleased(@NonNull Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#layoutSurface} is called to
+         * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to
          * change the layout of surface.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          * @param left Left position.
          * @param top Top position.
          * @param right Right position.
@@ -1618,85 +1700,90 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#requestCommand} is called.
+         * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          * @param cmdType type of the command.
          * @param parameters parameters of the command.
          */
         public void onCommandRequest(
                 Session session,
-                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 Bundle parameters) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#SetVideoBounds} is called.
+         * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
         public void onSetVideoBounds(Session session, Rect rect) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
          * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
         public void onRequestCurrentChannelUri(Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is
          * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
         public void onRequestCurrentChannelLcn(Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is
          * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
         public void onRequestStreamVolume(Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is
          * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          */
         public void onRequestTrackInfoList(Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentTvInputId} is
+         * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @hide
          */
         public void onRequestCurrentTvInputId(Session session) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called.
+         * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
+         * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          * @param state the current state.
          */
-        public void onSessionStateChanged(Session session, int state) {
+        public void onSessionStateChanged(
+                Session session,
+                @InteractiveAppState int state,
+                @ErrorCode int err) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#notifyBiInteractiveAppCreated}
+         * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated}
          * is called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
          *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
@@ -1709,11 +1796,11 @@
          * This is called when {@link TvIAppService.Session#notifyTeletextAppStateChanged} is
          * called.
          *
-         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
          * @param state the current state.
          */
         public void onTeletextAppStateChanged(
-                Session session, @TvIAppManager.TeletextAppState int state) {
+                Session session, @TvInteractiveAppManager.TeletextAppState int state) {
         }
     }
 }
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
similarity index 91%
rename from media/java/android/media/tv/interactive/TvIAppService.java
rename to media/java/android/media/tv/interactive/TvInteractiveAppService.java
index c0ec76b..afa1ff7 100755
--- a/media/java/android/media/tv/interactive/TvIAppService.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,11 +66,12 @@
 import java.util.List;
 
 /**
- * The TvIAppService 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 TvIAppService extends Service {
+public abstract class TvInteractiveAppService extends Service {
     private static final boolean DEBUG = false;
-    private static final String TAG = "TvIAppService";
+    private static final String TAG = "TvInteractiveAppService";
 
     private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
 
@@ -83,12 +85,12 @@
      * cannot abuse it.
      */
     public static final String SERVICE_INTERFACE =
-            "android.media.tv.interactive.TvIAppService";
+            "android.media.tv.interactive.TvInteractiveAppService";
 
     /**
-     * Name under which a TvIAppService component publishes information about itself. This
+     * Name under which a TvInteractiveAppService component publishes information about itself. This
      * meta-data must reference an XML resource containing an
-     * <code>&lt;{@link android.R.styleable#TvIAppService tv-interactive-app}&gt;</code>
+     * <code>&lt;{@link android.R.styleable#TvInteractiveAppService tv-interactive-app}&gt;</code>
      * tag.
      */
     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
@@ -131,6 +133,13 @@
     /** @hide */
     public static final String COMMAND_PARAMETER_KEY_TRACK_SELECT_MODE =
             "command_track_select_mode";
+    /**
+     * Command to quiet channel change. No channel banner or channel info is shown.
+     * <p>Refer to HbbTV Spec 2.0.4 chapter A.2.4.3.
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY =
+            "command_change_channel_quietly";
 
     private final Handler mServiceHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
@@ -175,12 +184,12 @@
             }
 
             @Override
-            public void registerAppLinkInfo(Bundle appLinkInfo) {
+            public void registerAppLinkInfo(AppLinkInfo appLinkInfo) {
                 onRegisterAppLinkInfo(appLinkInfo);
             }
 
             @Override
-            public void unregisterAppLinkInfo(Bundle appLinkInfo) {
+            public void unregisterAppLinkInfo(AppLinkInfo appLinkInfo) {
                 onUnregisterAppLinkInfo(appLinkInfo);
             }
 
@@ -194,17 +203,14 @@
 
     /**
      * Prepares TV Interactive App service for the given type.
-     * @hide
      */
-    public void onPrepare(int type) {
-        // TODO: make it abstract when unhide
-    }
+    public abstract void onPrepare(@TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
      * Registers App link info.
      * @hide
      */
-    public void onRegisterAppLinkInfo(Bundle appLinkInfo) {
+    public void onRegisterAppLinkInfo(AppLinkInfo appLinkInfo) {
         // TODO: make it abstract when unhide
     }
 
@@ -212,7 +218,7 @@
      * Unregisters App link info.
      * @hide
      */
-    public void onUnregisterAppLinkInfo(Bundle appLinkInfo) {
+    public void onUnregisterAppLinkInfo(AppLinkInfo appLinkInfo) {
         // TODO: make it abstract when unhide
     }
 
@@ -233,28 +239,41 @@
      *
      * @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(@NonNull String iAppServiceId, int type) {
-        // TODO: make it abstract when unhide
-        return null;
-    }
+    public abstract Session onCreateSession(
+            @NonNull String iAppServiceId,
+            @TvInteractiveAppInfo.InteractiveAppType int type);
 
     /**
-     * Notifies the system when the state of the interactive app has been changed.
-     * @param state the current state
-     * @hide
+     * Notifies the system when the state of the interactive app RTE has been changed.
+     *
+     * @param type the interactive app type
+     * @param state the current state of the service of the given type
+     * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
+     *              used when the state is not
+     *              {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
      */
     public final void notifyStateChanged(
-            int type, @TvIAppManager.TvInteractiveAppRteState int state) {
-        mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED,
-                type, state).sendToTarget();
+            @TvInteractiveAppInfo.InteractiveAppType int type,
+            @TvInteractiveAppManager.ServiceState int state,
+            @TvInteractiveAppManager.ErrorCode int error) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = type;
+        args.arg2 = state;
+        args.arg3 = error;
+        mServiceHandler
+                .obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, args).sendToTarget();
     }
 
     /**
      * 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();
@@ -298,6 +317,7 @@
          *
          * @param enable {@code true} if you want to enable the media view. {@code false}
          *            otherwise.
+         * @hide
          */
         public void setMediaViewEnabled(final boolean enable) {
             mHandler.post(new Runnable() {
@@ -319,15 +339,13 @@
         }
 
         /**
-         * Starts TvIAppService session.
-         * @hide
+         * Starts TvInteractiveAppService session.
          */
         public void onStartInteractiveApp() {
         }
 
         /**
-         * Stops TvIAppService session.
-         * @hide
+         * Stops TvInteractiveAppService session.
          */
         public void onStopInteractiveApp() {
         }
@@ -342,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) {
         }
@@ -353,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) {
         }
@@ -364,6 +384,7 @@
         /**
          * To toggle Digital Teletext Application if there is one in AIT app list.
          * @param enable
+         * @hide
          */
         public void onSetTeletextAppEnabled(boolean enable) {
         }
@@ -438,6 +459,7 @@
          *
          * @param width The width of the media view.
          * @param height The height of the media view.
+         * @hide
          */
         public void onMediaViewSizeChanged(int width, int height) {
         }
@@ -447,6 +469,7 @@
          * implementation can override this method and return its own view.
          *
          * @return a view attached to the media window
+         * @hide
          */
         @Nullable
         public View onCreateMediaView() {
@@ -454,7 +477,7 @@
         }
 
         /**
-         * Releases TvIAppService session.
+         * Releases TvInteractiveAppService session.
          * @hide
          */
         public void onRelease() {
@@ -462,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) {
         }
@@ -620,6 +644,7 @@
         /**
          * Requests broadcast related information from the related TV input.
          * @param request the request for broadcast info
+         * @hide
          */
         public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -644,6 +669,7 @@
         /**
          * Remove broadcast information request from the related TV input.
          * @param requestId the ID of the request
+         * @hide
          */
         public void removeBroadcastInfo(final int requestId) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -669,6 +695,7 @@
          * requests a specific command to be processed by the related TV input.
          * @param cmdType type of the specific command
          * @param parameters parameters of the specific command
+         * @hide
          */
         public void requestCommand(
                 @InteractiveAppServiceCommandType String cmdType, Bundle parameters) {
@@ -693,6 +720,7 @@
 
         /**
          * Sets broadcast video bounds.
+         * @hide
          */
         public void setVideoBounds(Rect rect) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -715,6 +743,7 @@
 
         /**
          * Requests the URI of the current channel.
+         * @hide
          */
         public void requestCurrentChannelUri() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -737,6 +766,7 @@
 
         /**
          * Requests the logic channel number (LCN) of the current channel.
+         * @hide
          */
         public void requestCurrentChannelLcn() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -759,6 +789,7 @@
 
         /**
          * Requests stream volume.
+         * @hide
          */
         public void requestStreamVolume() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -781,6 +812,7 @@
 
         /**
          * Requests the list of {@link TvTrackInfo}.
+         * @hide
          */
         public void requestTrackInfoList() {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -829,6 +861,7 @@
         /**
          * requests an advertisement request to be processed by the related TV input.
          * @param request advertisement request
+         * @hide
          */
         public void requestAd(@NonNull final AdRequest request) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -987,10 +1020,15 @@
 
         /**
          * Notifies when the session state is changed.
-         * @param state the current state.
+         *
+         * @param state the current session 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}.
          */
         public void notifySessionStateChanged(
-                @TvIAppManager.TvInteractiveAppRteState int state) {
+                @TvInteractiveAppManager.InteractiveAppState int state,
+                @TvInteractiveAppManager.ErrorCode int err) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -998,10 +1036,10 @@
                     try {
                         if (DEBUG) {
                             Log.d(TAG, "notifySessionStateChanged (state="
-                                    + state + ")");
+                                    + state + "; err=" + err + ")");
                         }
                         if (mSessionCallback != null) {
-                            mSessionCallback.onSessionStateChanged(state);
+                            mSessionCallback.onSessionStateChanged(state, err);
                         }
                     } catch (RemoteException e) {
                         Log.w(TAG, "error in notifySessionStateChanged", e);
@@ -1012,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
@@ -1039,8 +1080,10 @@
         /**
          * Notifies when the digital teletext app state is changed.
          * @param state the current state.
+         * @hide
          */
-        public final void notifyTeletextAppStateChanged(@TvIAppManager.TeletextAppState int state) {
+        public final void notifyTeletextAppStateChanged(
+                @TvInteractiveAppManager.TeletextAppState int state) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
@@ -1068,7 +1111,7 @@
             if (event instanceof KeyEvent) {
                 KeyEvent keyEvent = (KeyEvent) event;
                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
-                    return TvIAppManager.Session.DISPATCH_HANDLED;
+                    return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
                 }
 
                 // TODO: special handlings of navigation keys and media keys
@@ -1077,20 +1120,20 @@
                 final int source = motionEvent.getSource();
                 if (motionEvent.isTouchEvent()) {
                     if (onTouchEvent(motionEvent)) {
-                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                        return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
                     }
                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                     if (onTrackballEvent(motionEvent)) {
-                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                        return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
                     }
                 } else {
                     if (onGenericMotionEvent(motionEvent)) {
-                        return TvIAppManager.Session.DISPATCH_HANDLED;
+                        return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
                     }
                 }
             }
             // TODO: handle overlay view
-            return TvIAppManager.Session.DISPATCH_NOT_HANDLED;
+            return TvInteractiveAppManager.Session.DISPATCH_NOT_HANDLED;
         }
 
         private void initialize(ITvInteractiveAppSessionCallback callback) {
@@ -1443,9 +1486,9 @@
                 }
 
                 int handled = mSessionImpl.dispatchInputEvent(event, this);
-                if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) {
+                if (handled != TvInteractiveAppManager.Session.DISPATCH_IN_PROGRESS) {
                     finishInputEvent(
-                            event, handled == TvIAppManager.Session.DISPATCH_HANDLED);
+                            event, handled == TvInteractiveAppManager.Session.DISPATCH_HANDLED);
                 }
             }
         }
@@ -1457,11 +1500,11 @@
         private static final int DO_NOTIFY_SESSION_CREATED = 2;
         private static final int DO_NOTIFY_RTE_STATE_CHANGED = 3;
 
-        private void broadcastRteStateChanged(int type, int state) {
+        private void broadcastRteStateChanged(int type, int state, int error) {
             int n = mCallbacks.beginBroadcast();
             for (int i = 0; i < n; ++i) {
                 try {
-                    mCallbacks.getBroadcastItem(i).onStateChanged(type, state);
+                    mCallbacks.getBroadcastItem(i).onStateChanged(type, state, error);
                 } catch (RemoteException e) {
                     Log.e(TAG, "error in broadcastRteStateChanged", e);
                 }
@@ -1491,7 +1534,7 @@
                         return;
                     }
                     ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper(
-                            android.media.tv.interactive.TvIAppService.this, sessionImpl, channel);
+                            TvInteractiveAppService.this, sessionImpl, channel);
 
                     SomeArgs someArgs = SomeArgs.obtain();
                     someArgs.arg1 = sessionImpl;
@@ -1519,9 +1562,11 @@
                     return;
                 }
                 case DO_NOTIFY_RTE_STATE_CHANGED: {
-                    int type = msg.arg1;
-                    int state = msg.arg2;
-                    broadcastRteStateChanged(type, state);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int type = (int) args.arg1;
+                    int state = (int) args.arg2;
+                    int error = (int) args.arg3;
+                    broadcastRteStateChanged(type, state, error);
                     return;
                 }
                 default: {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 6f99d51..2922bae 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -22,14 +22,15 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
-import android.media.tv.interactive.TvIAppManager.Session;
-import android.media.tv.interactive.TvIAppManager.Session.FinishedInputEventCallback;
-import android.media.tv.interactive.TvIAppManager.SessionCallback;
+import android.media.tv.interactive.TvInteractiveAppManager.Session;
+import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
+import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -50,7 +51,6 @@
 
 /**
  * Displays contents of interactive TV applications.
- * @hide
  */
 public class TvInteractiveAppView extends ViewGroup {
     private static final String TAG = "TvInteractiveAppView";
@@ -61,7 +61,7 @@
     private static final int UNSET_TVVIEW_SUCCESS = 3;
     private static final int UNSET_TVVIEW_FAIL = 4;
 
-    private final TvIAppManager mTvInteractiveAppManager;
+    private final TvInteractiveAppManager mTvInteractiveAppManager;
     private final Handler mHandler = new Handler();
     private final Object mCallbackLock = new Object();
     private Session mSession;
@@ -141,8 +141,8 @@
         }
         mDefStyleAttr = defStyleAttr;
         resetSurfaceView();
-        mTvInteractiveAppManager = (TvIAppManager) getContext().getSystemService(
-                Context.TV_IAPP_SERVICE);
+        mTvInteractiveAppManager = (TvInteractiveAppManager) getContext().getSystemService(
+                Context.TV_INTERACTIVE_APP_SERVICE);
     }
 
     /**
@@ -152,8 +152,8 @@
      *                 callback.
      */
     public void setCallback(
-            @NonNull TvInteractiveAppCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TvInteractiveAppCallback callback) {
         synchronized (mCallbackLock) {
             mCallbackExecutor = executor;
             mCallback = callback;
@@ -238,6 +238,7 @@
         // The surface view's content should be treated as secure all the time.
         mSurfaceView.setSecure(true);
         mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
         addView(mSurfaceView);
     }
 
@@ -381,9 +382,15 @@
 
     /**
      * Prepares the interactive application.
-     * @hide
+     *
+     * @param iAppServiceId the interactive app service ID, which can be found in
+     *                      {@link TvInteractiveAppInfo#getId()}.
+     *
+     * @see android.media.tv.interactive.TvInteractiveAppManager#getTvInteractiveAppServiceList()
      */
-    public void prepareInteractiveApp(@NonNull String iAppServiceId, int type) {
+    public void prepareInteractiveApp(
+            @NonNull String iAppServiceId,
+            @TvInteractiveAppInfo.InteractiveAppType int type) {
         // TODO: document and handle the cases that this method is called multiple times.
         if (DEBUG) {
             Log.d(TAG, "prepareInteractiveApp");
@@ -408,7 +415,6 @@
 
     /**
      * Stops the interactive application.
-     * @hide
      */
     public void stopInteractiveApp() {
         if (DEBUG) {
@@ -516,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) {
@@ -534,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) {
@@ -552,11 +559,10 @@
 
     /**
      * Sets the TvInteractiveAppView to receive events from TIS. This method links the session of
-     * TvIAppManager to TvInputManager session, so the TIAS can get the TIS events.
+     * TvInteractiveAppManager to TvInputManager session, so the TIAS can get the TIS events.
      *
      * @param tvView the TvView to be linked to this TvInteractiveAppView via linking of Sessions.
-     * @return to be added
-     * @hide
+     * @return The result of the operation.
      */
     public int setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -583,6 +589,7 @@
     /**
      * To toggle Digital Teletext Application if there is one in AIT app list.
      * @param enable
+     * @hide
      */
     public void setTeletextAppEnabled(boolean enable) {
         if (DEBUG) {
@@ -609,18 +616,23 @@
          */
         public void onCommandRequest(
                 @NonNull String iAppServiceId,
-                @NonNull @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                @NonNull @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 @Nullable Bundle parameters) {
         }
 
         /**
-         * 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 current session state.
-         * @hide
+         * @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}.
          */
-        public void onSessionStateChanged(@NonNull String iAppServiceId, int state) {
+        public void onStateChanged(
+                @NonNull String iAppServiceId,
+                @TvInteractiveAppManager.InteractiveAppState int state,
+                @TvInteractiveAppManager.ErrorCode int err) {
         }
 
         /**
@@ -628,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) {
@@ -642,13 +656,15 @@
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @param state digital teletext app current state.
+         * @hide
          */
         public void onTeletextAppStateChanged(
-                @NonNull String iAppServiceId, @TvIAppManager.TeletextAppState int state) {
+                @NonNull String iAppServiceId,
+                @TvInteractiveAppManager.TeletextAppState int state) {
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#SetVideoBounds} is called.
+         * This is called when {@link TvInteractiveAppService.Session#SetVideoBounds} is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
          * @hide
@@ -657,7 +673,7 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelUri} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
          * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
@@ -667,7 +683,7 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestCurrentChannelLcn} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelLcn} is
          * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
@@ -677,7 +693,7 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestStreamVolume} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestStreamVolume} is
          * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
@@ -687,7 +703,7 @@
         }
 
         /**
-         * This is called when {@link TvIAppService.Session#RequestTrackInfoList} is
+         * This is called when {@link TvInteractiveAppService.Session#RequestTrackInfoList} is
          * called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
@@ -700,6 +716,7 @@
          * This is called when {@link TvIAppService.Session#RequestCurrentTvInputId} is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
          */
         public void onRequestCurrentTvInputId(@NonNull String iAppServiceId) {
         }
@@ -801,7 +818,7 @@
         @Override
         public void onCommandRequest(
                 Session session,
-                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 Bundle parameters) {
             if (DEBUG) {
                 Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
@@ -825,9 +842,12 @@
         }
 
         @Override
-        public void onSessionStateChanged(Session session, int state) {
+        public void onSessionStateChanged(
+                Session session,
+                @TvInteractiveAppManager.InteractiveAppState int state,
+                @TvInteractiveAppManager.ErrorCode int err) {
             if (DEBUG) {
-                Log.d(TAG, "onSessionStateChanged (state=" + state +  ")");
+                Log.d(TAG, "onSessionStateChanged (state=" + state + "; err=" + err + ")");
             }
             if (this != mSessionCallback) {
                 Log.w(TAG, "onSessionStateChanged - session not created");
@@ -838,7 +858,7 @@
                     mCallbackExecutor.execute(() -> {
                         synchronized (mCallbackLock) {
                             if (mCallback != null) {
-                                mCallback.onSessionStateChanged(mIAppServiceId, state);
+                                mCallback.onStateChanged(mIAppServiceId, state, err);
                             }
                         }
                     });
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 9c4a83a..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;
@@ -1002,9 +1001,10 @@
     private native String nativeGetFrontendHardwareInfo();
     private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber);
     private native int nativeGetMaxNumberOfFrontends(int frontendType);
-
+    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);
@@ -1565,6 +1565,68 @@
     }
 
     /**
+     * Filter out unnecessary PID (packet identifier) from frontend output.
+     *
+     * <p>It is used by the client to remove some video or audio PIDs of other program to reduce the
+     * total amount of recorded TS.
+     *
+     * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause
+     * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * @return result status of the operation. Unsupported version or if current active frontend
+     *         doesn’t support PID filtering out would return {@link #RESULT_UNAVAILABLE}.
+     * @throws IllegalStateException if there is no active frontend currently.
+     */
+    @Result
+    public int removeOutputPid(@IntRange(from = 0) int pid) {
+        mFrontendLock.lock();
+        try {
+            if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
+                return RESULT_UNAVAILABLE;
+            }
+            if (mFrontend == null) {
+                throw new IllegalStateException("frontend is not initialized");
+            }
+            return nativeRemoveOutputPid(pid);
+        } finally {
+            mFrontendLock.unlock();
+        }
+    }
+
+    /**
+     * 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/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java
index f123675..83ed8e8 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettings.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -82,7 +82,7 @@
      * The section filter uses this for CRC (Cyclic redundancy check) checking when
      * {@link #isCrcEnabled()} is {@code true}.
      */
-    public int getBitWidthOfLengthField() {
+    public int getLengthFieldBitWidth() {
         return mBitWidthOfLengthField;
     }
 
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 8cedd04..c1e9b38 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -54,7 +54,7 @@
             FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
             FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
             FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_IDS,
-            FRONTEND_STATUS_TYPE_DVBT_CELL_IDS})
+            FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendStatusType {}
 
@@ -165,7 +165,7 @@
     public static final int FRONTEND_STATUS_TYPE_RF_LOCK =
             android.hardware.tv.tuner.FrontendStatusType.RF_LOCK;
     /**
-     * PLP information in a frequency band for ATSC-3.0 frontend.
+     * Current tuned PLP information in a frequency band for ATSC-3.0 frontend.
      */
     public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
             android.hardware.tv.tuner.FrontendStatusType.ATSC3_PLP_INFO;
@@ -267,6 +267,13 @@
     public static final int FRONTEND_STATUS_TYPE_DVBT_CELL_IDS =
             android.hardware.tv.tuner.FrontendStatusType.DVBT_CELL_IDS;
 
+    /**
+     * All PLP information in a frequency band for ATSC-3.0 frontend, which includes both tuned and
+     * not tuned PLPs for currently watching service.
+     */
+    public static final int FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO =
+            android.hardware.tv.tuner.FrontendStatusType.ATSC3_ALL_PLP_INFO;
+
     /** @hide */
     @IntDef(value = {
             AtscFrontendSettings.MODULATION_UNDEFINED,
@@ -508,6 +515,7 @@
     private Integer mIsdbtPartialReceptionFlag;
     private int[] mStreamIds;
     private int[] mDvbtCellIds;
+    private Atsc3PlpInfo[] mAllPlpInfo;
 
     // Constructed and fields set by JNI code.
     private FrontendStatus() {
@@ -1078,6 +1086,25 @@
     }
 
     /**
+     * Gets an array of all PLPs information of ATSC3 frontend, which includes both tuned and not
+     * tuned PLPs for currently watching service.
+     *
+     * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
+     * doesn't return all PLPs information will throw IllegalStateException. Use
+     * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     */
+    @SuppressLint("ArrayReturn")
+    @NonNull
+    public Atsc3PlpInfo[] getAllAtsc3PlpInfo() {
+        TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_2_0, "Atsc3PlpInfo all status");
+        if (mAllPlpInfo == null) {
+            throw new IllegalStateException("Atsc3PlpInfo all status is empty");
+        }
+        return mAllPlpInfo;
+    }
+
+    /**
      * Information of each tuning Physical Layer Pipes.
      */
     public static class Atsc3PlpTuningInfo {
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_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 0a5490d..2e419a6 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -375,7 +375,8 @@
 }
 
 static jlong ImageWriter_init(JNIEnv* env, jobject thiz, jobject weakThiz, jobject jsurface,
-        jint maxImages, jint userFormat, jint userWidth, jint userHeight) {
+        jint maxImages, jint userWidth, jint userHeight, jboolean useSurfaceImageFormatInfo,
+        jint hardwareBufferFormat, jlong dataSpace, jlong ndkUsage) {
     status_t res;
 
     ALOGV("%s: maxImages:%d", __FUNCTION__, maxImages);
@@ -450,7 +451,7 @@
 
     // Query surface format if no valid user format is specified, otherwise, override surface format
     // with user format.
-    if (userFormat == IMAGE_FORMAT_UNKNOWN) {
+    if (useSurfaceImageFormatInfo) {
         if ((res = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &surfaceFormat)) != OK) {
             ALOGE("%s: Query Surface format failed: %s (%d)", __FUNCTION__, strerror(-res), res);
             jniThrowRuntimeException(env, "Failed to query Surface format");
@@ -458,13 +459,13 @@
         }
     } else {
         // Set consumer buffer format to user specified format
-        PublicFormat publicFormat = static_cast<PublicFormat>(userFormat);
-        int nativeFormat = mapPublicFormatToHalFormat(publicFormat);
-        android_dataspace nativeDataspace = mapPublicFormatToHalDataspace(publicFormat);
-        res = native_window_set_buffers_format(anw.get(), nativeFormat);
+        android_dataspace nativeDataspace = static_cast<android_dataspace>(dataSpace);
+        int userFormat = static_cast<int>(mapHalFormatDataspaceToPublicFormat(
+            hardwareBufferFormat, nativeDataspace));
+        res = native_window_set_buffers_format(anw.get(), hardwareBufferFormat);
         if (res != OK) {
             ALOGE("%s: Unable to configure consumer native buffer format to %#x",
-                    __FUNCTION__, nativeFormat);
+                    __FUNCTION__, hardwareBufferFormat);
             jniThrowRuntimeException(env, "Failed to set Surface format");
             return 0;
         }
@@ -484,15 +485,13 @@
     env->SetIntField(thiz,
             gImageWriterClassInfo.mWriterFormat, reinterpret_cast<jint>(surfaceFormat));
 
-    if (!isFormatOpaque(surfaceFormat)) {
-        res = native_window_set_usage(anw.get(), GRALLOC_USAGE_SW_WRITE_OFTEN);
-        if (res != OK) {
-            ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)",
-                  __FUNCTION__, static_cast<unsigned int>(GRALLOC_USAGE_SW_WRITE_OFTEN),
-                  surfaceFormat, strerror(-res), res);
-            jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage");
-            return 0;
-        }
+    res = native_window_set_usage(anw.get(), ndkUsage);
+    if (res != OK) {
+        ALOGE("%s: Configure usage %08x for format %08x failed: %s (%d)",
+              __FUNCTION__, static_cast<unsigned int>(ndkUsage),
+              surfaceFormat, strerror(-res), res);
+        jniThrowRuntimeException(env, "Failed to SW_WRITE_OFTEN configure usage");
+        return 0;
     }
 
     int minUndequeuedBufferCount = 0;
@@ -1093,7 +1092,7 @@
 
 static JNINativeMethod gImageWriterMethods[] = {
     {"nativeClassInit",         "()V",                        (void*)ImageWriter_classInit },
-    {"nativeInit",              "(Ljava/lang/Object;Landroid/view/Surface;IIII)J",
+    {"nativeInit",              "(Ljava/lang/Object;Landroid/view/Surface;IIIZIJJ)J",
                                                               (void*)ImageWriter_init },
     {"nativeClose",              "(J)V",                      (void*)ImageWriter_close },
     {"nativeAttachAndQueueImage",
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 1b41494..68dd8d0 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1622,6 +1622,45 @@
     return mTunerClient->getMaxNumberOfFrontends(static_cast<FrontendType>(type));
 }
 
+jint JTuner::removeOutputPid(int32_t pid) {
+    if (mFeClient == nullptr) {
+        ALOGE("frontend is not initialized");
+        return (jint)Result::INVALID_STATE;
+    }
+
+    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;
@@ -2610,6 +2649,24 @@
                 env->SetObjectField(statusObj, field, valObj);
                 break;
             }
+            case FrontendStatus::Tag::allPlpInfo: {
+                jfieldID field = env->GetFieldID(clazz, "mAllPlpInfo",
+                                                 "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;");
+                jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
+                jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+
+                vector<FrontendScanAtsc3PlpInfo> plpInfos =
+                        s.get<FrontendStatus::Tag::allPlpInfo>();
+                jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+                for (int i = 0; i < plpInfos.size(); i++) {
+                    jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
+                                                    plpInfos[i].bLlsFlag);
+                    env->SetObjectArrayElement(valObj, i, plpObj);
+                }
+
+                env->SetObjectField(statusObj, field, valObj);
+                break;
+            }
         }
     }
     return statusObj;
@@ -4357,6 +4414,17 @@
     return tuner->getMaxNumberOfFrontends(type);
 }
 
+static jint android_media_tv_Tuner_remove_output_pid(JNIEnv *env, jobject thiz, jint pid) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    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();
@@ -4676,6 +4744,11 @@
              (void *)android_media_tv_Tuner_set_maximum_frontends },
     { "nativeGetMaxNumberOfFrontends", "(I)I",
             (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 502bd6b..03e7fa9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -205,6 +205,8 @@
     Result getFrontendHardwareInfo(string& info);
     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 0fdd8d8..c6337ec 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -143,6 +143,25 @@
     return Result::INVALID_STATE;
 }
 
+Result FrontendClient::removeOutputPid(int32_t pid) {
+    if (mTunerFrontend != nullptr) {
+        Status s = mTunerFrontend->removeOutputPid(pid);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
+
+    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 77d9098..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;
@@ -120,6 +121,17 @@
      */
     Result getHardwareInfo(string& info);
 
+    /**
+     * Filter out unnecessary PID from frontend output.
+     */
+    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/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp
index 8a573fb..1452488 100644
--- a/media/native/midi/MidiDeviceInfo.cpp
+++ b/media/native/midi/MidiDeviceInfo.cpp
@@ -64,6 +64,7 @@
     RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames));
     RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames));
     RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0));
+    RETURN_IF_FAILED(parcel->writeInt32(mDefaultProtocol));
     RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
     // This corresponds to "extra" properties written by Java code
     RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
@@ -83,6 +84,7 @@
     int32_t isPrivate;
     RETURN_IF_FAILED(parcel->readInt32(&isPrivate));
     mIsPrivate = isPrivate == 1;
+    RETURN_IF_FAILED(parcel->readInt32(&mDefaultProtocol));
     RETURN_IF_FAILED(mProperties.readFromParcel(parcel));
     // Ignore "extra" properties as they may contain Java Parcelables
     return OK;
@@ -130,7 +132,8 @@
             areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) &&
             areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) &&
             lhs.mProperties == rhs.mProperties &&
-            lhs.mIsPrivate == rhs.mIsPrivate);
+            lhs.mIsPrivate == rhs.mIsPrivate &&
+            lhs.mDefaultProtocol == rhs.mDefaultProtocol);
 }
 
 }  // namespace midi
diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h
index 5b4a241..23e1cb4 100644
--- a/media/native/midi/MidiDeviceInfo.h
+++ b/media/native/midi/MidiDeviceInfo.h
@@ -38,6 +38,7 @@
     int getType() const { return mType; }
     int getUid() const { return mId; }
     bool isPrivate() const { return mIsPrivate; }
+    int getDefaultProtocol() const { return mDefaultProtocol; }
     const Vector<String16>& getInputPortNames() const { return mInputPortNames; }
     const Vector<String16>&  getOutputPortNames() const { return mOutputPortNames; }
     String16 getProperty(const char* propertyName);
@@ -48,6 +49,18 @@
         TYPE_VIRTUAL = 2,
         TYPE_BLUETOOTH = 3,
     };
+
+    enum {
+        PROTOCOL_UMP_USE_MIDI_CI = 0,
+        PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1,
+        PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2,
+        PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3,
+        PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4,
+        PROTOCOL_UMP_MIDI_2_0 = 17,
+        PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18,
+        PROTOCOL_UNKNOWN = -1,
+    };
+
     static const char* const PROPERTY_NAME;
     static const char* const PROPERTY_MANUFACTURER;
     static const char* const PROPERTY_PRODUCT;
@@ -72,6 +85,7 @@
     Vector<String16> mOutputPortNames;
     os::PersistableBundle mProperties;
     bool mIsPrivate;
+    int32_t mDefaultProtocol;
 };
 
 }  // namespace midi
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index f90796e..aa076e8 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -138,6 +138,7 @@
     outDeviceInfoPtr->type = deviceInfo.getType();
     outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
     outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+    outDeviceInfoPtr->defaultProtocol = deviceInfo.getDefaultProtocol();
 
     return AMEDIA_OK;
 }
@@ -238,6 +239,13 @@
     return device->deviceInfo.outputPortCount;
 }
 
+AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) {
+    if (device == nullptr) {
+        return AMIDI_DEVICE_PROTOCOL_UNKNOWN;
+    }
+    return static_cast<AMidiDevice_Protocol>(device->deviceInfo.defaultProtocol);
+}
+
 /*
  * Port Helpers
  */
diff --git a/media/native/midi/amidi_internal.h b/media/native/midi/amidi_internal.h
index fce8596..023a6f5 100644
--- a/media/native/midi/amidi_internal.h
+++ b/media/native/midi/amidi_internal.h
@@ -25,6 +25,7 @@
     int32_t type;            /* one of AMIDI_DEVICE_TYPE_* constants */
     int32_t inputPortCount;  /* number of input (send) ports associated with the device */
     int32_t outputPortCount; /* number of output (received) ports associated with the device */
+    int32_t defaultProtocol; /* one of the AMIDI_DEVICE_PROTOCOL_* constants */
 } AMidiDeviceInfo;
 
 struct AMidiDevice {
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index 742db34..fbb7fb3 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -62,6 +62,78 @@
 };
 
 /*
+ * Protocol IDs for various MIDI devices.
+ *
+ * Introduced in API 33.
+ */
+enum AMidiDevice_Protocol : int32_t {
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use UMP to negotiate with the device with MIDI-CI.
+     * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI = 0,
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 64 bits.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1,
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2,
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 128 bits.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3,
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4,
+
+    /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 2.0 through UMP.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 = 17,
+
+     /**
+     * Constant representing a default protocol with Universal MIDI Packets (UMP).
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * All UMP data should be a multiple of 4 bytes.
+     * Use MIDI 2.0 through UMP and jitter reduction timestamps.
+     */
+    AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18,
+
+    /**
+     * Constant representing a device with an unknown default protocol.
+     * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0.
+     * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+     * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+     */
+    AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
+};
+
+/*
  * Device API
  */
 /**
@@ -134,6 +206,30 @@
  */
 ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29);
 
+/**
+ * Gets the MIDI device default protocol.
+ *
+ * @param device Specifies the MIDI device.
+ *
+ * @return The identifier of the MIDI device default protocol:
+ * AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UNKNOWN
+ *
+ * Most devices should return PROTOCOL_UNKNOWN (-1). This is intentional as devices
+ * with default UMP support are not backwards compatible. When the device is null,
+ * return AMIDI_DEVICE_PROTOCOL_UNKNOWN.
+ *
+ * Available since API 33.
+ */
+AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device)
+        __INTRODUCED_IN(33);
+
 /*
  * API for receiving data from the Output port of a device.
  */
diff --git a/media/native/midi/libamidi.map.txt b/media/native/midi/libamidi.map.txt
index 62627f8..f25f977 100644
--- a/media/native/midi/libamidi.map.txt
+++ b/media/native/midi/libamidi.map.txt
@@ -2,6 +2,7 @@
   global:
     AMidiDevice_fromJava;
     AMidiDevice_release;
+    AMidiDevice_getDefaultProtocol; # introduced=Tiramisu
     AMidiDevice_getType;
     AMidiDevice_getNumInputPorts;
     AMidiDevice_getNumOutputPorts;
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 62c313a..4c3b689 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -24,6 +24,7 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.media.midi.MidiDevice;
 import android.media.midi.MidiDeviceInfo;
 import android.media.midi.MidiDeviceServer;
 import android.media.midi.MidiDeviceStatus;
@@ -63,6 +64,7 @@
             "00002902-0000-1000-8000-00805f9b34fb");
 
     private final BluetoothDevice mBluetoothDevice;
+    private final Context mContext;
     private final BluetoothMidiService mService;
     private final MidiManager mMidiManager;
     private MidiReceiver mOutputReceiver;
@@ -136,6 +138,8 @@
                         // switch to receiving notifications
                         mBluetoothGatt.readCharacteristic(characteristic);
                     }
+
+                    openBluetoothDevice(mBluetoothDevice);
                 }
             } else {
                 Log.e(TAG, "onServicesDiscovered received: " + status);
@@ -249,6 +253,7 @@
 
         mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
 
+        mContext = context;
         mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
 
         Bundle properties = new Bundle();
@@ -260,7 +265,8 @@
         inputPortReceivers[0] = mEventScheduler.getReceiver();
 
         mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
-                null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback);
+                null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH,
+                MidiDeviceInfo.PROTOCOL_UNKNOWN, mDeviceServerCallback);
 
         mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
 
@@ -309,6 +315,18 @@
         }
     }
 
+    void openBluetoothDevice(BluetoothDevice btDevice) {
+        Log.d(TAG, "openBluetoothDevice() device: " + btDevice);
+
+        MidiManager midiManager = mContext.getSystemService(MidiManager.class);
+        midiManager.openBluetoothDevice(btDevice,
+                new MidiManager.OnDeviceOpenedListener() {
+                    @Override
+                    public void onDeviceOpened(MidiDevice device) {
+                    }
+                }, null);
+    }
+
     public IBinder getBinder() {
         return mDeviceServer.asBinder();
     }
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/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
new file mode 100644
index 0000000..a1855fd
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -0,0 +1,73 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/activity_confirmation"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="@drawable/dialog_background"
+              android:elevation="16dp"
+              android:maxHeight="400dp"
+              android:orientation="vertical"
+              android:padding="18dp"
+              android:layout_gravity="center">
+
+    <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
+
+    <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            style="@*android:style/TextAppearance.Widget.Toolbar.Title"/>
+
+    <TextView
+            android:id="@+id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:layout_marginBottom="12dp"
+            android:gravity="center"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="14sp" />
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:gravity="end">
+
+        <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+        <Button
+                android:id="@+id/btn_negative"
+                style="@android:style/Widget.Material.Button.Borderless.Colored"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/consent_no"
+                android:textColor="?android:attr/textColorSecondary" />
+
+        <Button
+                android:id="@+id/btn_positive"
+                style="@android:style/Widget.Material.Button.Borderless.Colored"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/consent_yes" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index cb8b616..25ec9606 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -73,4 +73,15 @@
 
     <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_no">Don\u2019t allow</string>
+
+    <!-- ================== System data transfer ==================== -->
+    <!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=60] -->
+    <string name="permission_sync_confirmation_title">Transfer app permissions to your
+        watch</string>
+
+    <!-- Text of the permission sync explanation in the confirmation dialog. [CHAR LIMIT=400] -->
+    <string name="permission_sync_summary">To make it easier to set up your watch,
+        apps installed on your watch during setup will use the same permissions as your phone.\n\n
+        These permissions may include access to your watch\u2019s microphone and location.</string>
+
 </resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java
new file mode 100644
index 0000000..67efa03
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDataTransferActivity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.companiondevicemanager;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static java.util.Objects.requireNonNull;
+
+import android.app.Activity;
+import android.companion.SystemDataTransferRequest;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.text.Html;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * This activity manages the UI of companion device data transfer.
+ */
+public class CompanionDeviceDataTransferActivity extends Activity {
+
+    private static final String LOG_TAG = CompanionDeviceDataTransferActivity.class.getSimpleName();
+
+    // UI -> SystemDataTransferProcessor
+    private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED = 0;
+    private static final int RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED = 1;
+    private static final String EXTRA_SYSTEM_DATA_TRANSFER_REQUEST = "system_data_transfer_request";
+    private static final String EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER =
+            "system_data_transfer_result_receiver";
+
+    private SystemDataTransferRequest mRequest;
+    private ResultReceiver mCdmServiceReceiver;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.i(LOG_TAG, "Creating UI for data transfer confirmation.");
+
+        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+        setContentView(R.layout.data_transfer_confirmation);
+
+        TextView titleView = findViewById(R.id.title);
+        TextView summaryView = findViewById(R.id.summary);
+        ListView listView = findViewById(R.id.device_list);
+        listView.setVisibility(View.GONE);
+        Button allowButton = findViewById(R.id.btn_positive);
+        Button disallowButton = findViewById(R.id.btn_negative);
+
+        final Intent intent = getIntent();
+        mRequest = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST);
+        mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_SYSTEM_DATA_TRANSFER_RESULT_RECEIVER);
+
+        requireNonNull(mRequest);
+        requireNonNull(mCdmServiceReceiver);
+
+        if (mRequest.isPermissionSyncAllPackages()
+                || !mRequest.getPermissionSyncPackages().isEmpty()) {
+            titleView.setText(Html.fromHtml(getString(
+                    R.string.permission_sync_confirmation_title), 0));
+            summaryView.setText(getString(R.string.permission_sync_summary));
+            allowButton.setOnClickListener(v -> allow());
+            disallowButton.setOnClickListener(v -> disallow());
+        }
+    }
+
+    private void allow() {
+        Log.i(LOG_TAG, "allow()");
+
+        sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED);
+
+        setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED);
+    }
+
+    private void disallow() {
+        Log.i(LOG_TAG, "disallow()");
+
+        sendDataToReceiver(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED);
+
+        setResultAndFinish(RESULT_CODE_SYSTEM_DATA_TRANSFER_DISALLOWED);
+    }
+
+    private void sendDataToReceiver(int cdmResultCode) {
+        Bundle data = new Bundle();
+        data.putParcelable(EXTRA_SYSTEM_DATA_TRANSFER_REQUEST, mRequest);
+        mCdmServiceReceiver.send(cdmResultCode, data);
+    }
+
+    private void setResultAndFinish(int cdmResultCode) {
+        setResult(cdmResultCode == RESULT_CODE_SYSTEM_DATA_TRANSFER_ALLOWED
+                ? RESULT_OK : RESULT_CANCELED);
+        finish();
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index d3d8bba..223bdcdd 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -129,6 +129,11 @@
         "src/android/net/EthernetNetworkSpecifier.java",
         "src/android/net/IEthernetManager.aidl",
         "src/android/net/IEthernetServiceListener.aidl",
+        "src/android/net/IInternalNetworkManagementListener.aidl",
+        "src/android/net/InternalNetworkUpdateRequest.java",
+        "src/android/net/InternalNetworkUpdateRequest.aidl",
+        "src/android/net/InternalNetworkManagementException.java",
+        "src/android/net/InternalNetworkManagementException.aidl",
         "src/android/net/ITetheredInterfaceCallback.aidl",
     ],
     path: "src",
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
index d33666d..2b6570a 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
@@ -556,7 +556,7 @@
     /**
      * Collects history results for uid and resets history enumeration index.
      */
-    void startHistoryEnumeration(int uid, int tag, int state) {
+    void startHistoryUidEnumeration(int uid, int tag, int state) {
         mHistory = null;
         try {
             mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
@@ -571,6 +571,20 @@
     }
 
     /**
+     * Collects history results for network and resets history enumeration index.
+     */
+    void startHistoryDeviceEnumeration() {
+        try {
+            mHistory = mSession.getHistoryIntervalForNetwork(
+                    mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+            mHistory = null;
+        }
+        mEnumerationIndex = 0;
+    }
+
+    /**
      * Starts uid enumeration for current user.
      * @throws RemoteException
      */
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 d00de36..28f930f 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -17,8 +17,11 @@
 package android.app.usage;
 
 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+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;
@@ -37,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;
@@ -55,7 +55,7 @@
 
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Provides access to network usage history and statistics. Usage data is collected in
@@ -125,6 +125,19 @@
     private final Context mContext;
     private final INetworkStatsService mService;
 
+    /**
+     * Type constants for reading different types of Data Usage.
+     * @hide
+     */
+    // @SystemApi(client = MODULE_LIBRARIES)
+    public static final String PREFIX_DEV = "dev";
+    /** @hide */
+    public static final String PREFIX_XT = "xt";
+    /** @hide */
+    public static final String PREFIX_UID = "uid";
+    /** @hide */
+    public static final String PREFIX_UID_TAG = "uid_tag";
+
     /** @hide */
     public static final int FLAG_POLL_ON_OPEN = 1 << 0;
     /** @hide */
@@ -143,6 +156,11 @@
         setAugmentWithSubscriptionPlan(true);
     }
 
+    /** @hide */
+    public INetworkStatsService getBinder() {
+        return mService;
+    }
+
     /**
      * Set poll on open flag to indicate the poll is needed before service gets statistics
      * result. This is default enabled. However, for any non-privileged caller, the poll might
@@ -211,9 +229,10 @@
      */
     @NonNull
     @WorkerThread
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     public Bucket querySummaryForDevice(@NonNull NetworkTemplate template,
             long startTime, long endTime) {
+        Objects.requireNonNull(template);
         try {
             NetworkStats stats =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -385,10 +404,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats querySummary(@NonNull NetworkTemplate template, long startTime,
             long endTime) throws SecurityException {
+        Objects.requireNonNull(template);
         try {
             NetworkStats result =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -418,10 +438,11 @@
      * @hide
      */
     @NonNull
-    // @SystemApi(client = MODULE_LIBRARIES)
+    @SystemApi(client = MODULE_LIBRARIES)
     @WorkerThread
     public NetworkStats queryTaggedSummary(@NonNull NetworkTemplate template, long startTime,
             long endTime) throws SecurityException {
+        Objects.requireNonNull(template);
         try {
             NetworkStats result =
                     new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
@@ -434,6 +455,43 @@
     }
 
     /**
+     * Query usage statistics details for networks matching a given {@link NetworkTemplate}.
+     *
+     * Result is not aggregated over time. This means buckets' start and
+     * end timestamps will be between 'startTime' and 'endTime' parameters.
+     * <p>Only includes buckets whose entire time period is included between
+     * startTime and endTime. Doesn't interpolate or return partial buckets.
+     * Since bucket length is in the order of hours, this
+     * method cannot be used to measure data usage on a fine grained time scale.
+     * This may take a long time, and apps should avoid calling this on their main thread.
+     *
+     * @param template Template used to match networks. See {@link NetworkTemplate}.
+     * @param startTime Start of period, in milliseconds since the Unix epoch, see
+     *                  {@link java.lang.System#currentTimeMillis}.
+     * @param endTime End of period, in milliseconds since the Unix epoch, see
+     *                {@link java.lang.System#currentTimeMillis}.
+     * @return Statistics which is described above.
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = MODULE_LIBRARIES)
+    @WorkerThread
+    public NetworkStats queryDetailsForDevice(@NonNull NetworkTemplate template,
+            long startTime, long endTime) {
+        Objects.requireNonNull(template);
+        try {
+            final NetworkStats result =
+                    new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
+            result.startHistoryDeviceEnumeration();
+            return result;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        return null; // To make the compiler happy.
+    }
+
+    /**
      * Query network usage statistics details for a given uid.
      * This may take a long time, and apps should avoid calling this on their main thread.
      *
@@ -499,7 +557,8 @@
      * @param endTime End of period. Defined in terms of "Unix time", see
      *            {@link java.lang.System#currentTimeMillis}.
      * @param uid UID of app
-     * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags.
+     * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+     *            across all the tags.
      * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
      *            traffic from all states.
      * @return Statistics object or null if an error happened during statistics collection.
@@ -514,21 +573,52 @@
         return queryDetailsForUidTagState(template, startTime, endTime, uid, tag, state);
     }
 
-    /** @hide */
-    public NetworkStats queryDetailsForUidTagState(NetworkTemplate template,
+    /**
+     * Query network usage statistics details for a given template, uid, tag, and state.
+     *
+     * Only usable for uids belonging to calling user. Result is not aggregated over time.
+     * This means buckets' start and end timestamps are going to be between 'startTime' and
+     * 'endTime' parameters. The uid is going to be the same as the 'uid' parameter, the tag
+     * the same as the 'tag' parameter, and the state the same as the 'state' parameter.
+     * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
+     * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
+     * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
+     * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
+     * interpolate across partial buckets. Since bucket length is in the order of hours, this
+     * method cannot be used to measure data usage on a fine grained time scale.
+     * This may take a long time, and apps should avoid calling this on their main thread.
+     *
+     * @param template Template used to match networks. See {@link NetworkTemplate}.
+     * @param startTime Start of period, in milliseconds since the Unix epoch, see
+     *                  {@link java.lang.System#currentTimeMillis}.
+     * @param endTime End of period, in milliseconds since the Unix epoch, see
+     *                {@link java.lang.System#currentTimeMillis}.
+     * @param uid UID of app
+     * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for aggregated data
+     *            across all the tags.
+     * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
+     *            traffic from all states.
+     * @return Statistics which is described above.
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = MODULE_LIBRARIES)
+    @WorkerThread
+    public NetworkStats queryDetailsForUidTagState(@NonNull NetworkTemplate template,
             long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
-
-        NetworkStats result;
+        Objects.requireNonNull(template);
         try {
-            result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
-            result.startHistoryEnumeration(uid, tag, state);
+            final NetworkStats result = new NetworkStats(
+                    mContext, template, mFlags, startTime, endTime, mService);
+            result.startHistoryUidEnumeration(uid, tag, state);
+            return result;
         } catch (RemoteException e) {
             Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag
                     + " state=" + state, e);
-            return null;
+            e.rethrowFromSystemServer();
         }
 
-        return result;
+        return null; // To make the compiler happy.
     }
 
     /**
@@ -585,50 +675,83 @@
     }
 
     /**
-     * Query realtime network usage statistics details with interfaces constrains.
-     * Return snapshot of current UID statistics, including any {@link TrafficStats#UID_TETHERING},
-     * video calling data usage and count of network operations that set by
-     * {@link TrafficStats#incrementOperationCount}. The returned data doesn't include any
-     * statistics that is reported by {@link NetworkStatsProvider}.
+     * Query realtime mobile network usage statistics.
      *
-     * @param requiredIfaces A list of interfaces the stats should be restricted to, or
-     *               {@link NetworkStats#INTERFACES_ALL}.
+     * Return a snapshot of current UID network statistics, as it applies
+     * to the mobile radios of the device. The snapshot will include any
+     * tethering traffic, video calling data usage and count of
+     * network operations set by {@link TrafficStats#incrementOperationCount}
+     * made over a mobile radio.
+     * The snapshot will not include any statistics that cannot be seen by
+     * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
      *
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
-    @NonNull public android.net.NetworkStats getDetailedUidStats(
-                @NonNull Set<String> requiredIfaces) {
-        Objects.requireNonNull(requiredIfaces, "requiredIfaces cannot be null");
+    @NonNull public android.net.NetworkStats getMobileUidStats() {
         try {
-            return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0]));
+            return mService.getUidStatsForTransport(TRANSPORT_CELLULAR);
         } catch (RemoteException e) {
-            if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats");
+            if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats");
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /** @hide */
-    public void registerUsageCallback(NetworkTemplate template, int networkType,
-            long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
-        Objects.requireNonNull(callback, "UsageCallback cannot be null");
-
-        final Looper looper;
-        if (handler == null) {
-            looper = Looper.myLooper();
-        } else {
-            looper = handler.getLooper();
+    /**
+     * Query realtime Wi-Fi network usage statistics.
+     *
+     * Return a snapshot of current UID network statistics, as it applies
+     * to the Wi-Fi radios of the device. The snapshot will include any
+     * tethering traffic, video calling data usage and count of
+     * network operations set by {@link TrafficStats#incrementOperationCount}
+     * made over a Wi-Fi radio.
+     * The snapshot will not include any statistics that cannot be seen by
+     * the kernel, e.g. statistics reported by {@link NetworkStatsProvider}s.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+    @NonNull public android.net.NetworkStats getWifiUidStats() {
+        try {
+            return mService.getUidStatsForTransport(TRANSPORT_WIFI);
+        } catch (RemoteException e) {
+            if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats");
+            throw e.rethrowFromSystemServer();
         }
+    }
 
-        DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
+    /**
+     * 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 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) {
@@ -681,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);
     }
 
     /**
@@ -711,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.
@@ -721,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;
+            }
+        }
     }
 
     /**
@@ -839,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/DataUsageRequest.java b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
index b06d515..f0ff465 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
@@ -75,7 +75,7 @@
                 @Override
                 public DataUsageRequest createFromParcel(Parcel in) {
                     int requestId = in.readInt();
-                    NetworkTemplate template = in.readParcelable(null);
+                    NetworkTemplate template = in.readParcelable(null, android.net.NetworkTemplate.class);
                     long thresholdInBytes = in.readLong();
                     DataUsageRequest result = new DataUsageRequest(requestId, template,
                             thresholdInBytes);
diff --git a/core/java/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
similarity index 100%
rename from core/java/android/net/IInternalNetworkManagementListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl
diff --git a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
index a4babb5..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;
@@ -49,14 +50,8 @@
     @UnsupportedAppUsage
     NetworkStats getDataLayerSnapshotForUid(int uid);
 
-    /** Get a detailed snapshot of stats since boot for all UIDs.
-    *
-    * <p>Results will not always be limited to stats on requiredIfaces when specified: stats for
-    * interfaces stacked on the specified interfaces, or for interfaces on which the specified
-    * interfaces are stacked on, will also be included.
-    * @param requiredIfaces Interface names to get data for, or {@link NetworkStats#INTERFACES_ALL}.
-    */
-    NetworkStats getDetailedUidStats(in String[] requiredIfaces);
+    /** Get the transport NetworkStats for all UIDs since boot. */
+    NetworkStats getUidStatsForTransport(int transport);
 
     /** Return set of any ifaces associated with mobile networks since boot. */
     @UnsupportedAppUsage
@@ -77,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/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
index babe0bf..ab70be8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
@@ -32,6 +32,11 @@
     /** Return historical network layer stats for traffic that matches template. */
     @UnsupportedAppUsage
     NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
+    /**
+     * Return historical network layer stats for traffic that matches template, start and end
+     * timestamp.
+     */
+    NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end);
 
     /**
      * Return network layer usage summary per UID for traffic that matches template.
diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl
diff --git a/core/java/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
similarity index 100%
rename from core/java/android/net/InternalNetworkManagementException.java
rename to packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl
diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
similarity index 100%
rename from core/java/android/net/InternalNetworkUpdateRequest.java
rename to packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
index 575c5ed..03bb187 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
@@ -267,14 +267,14 @@
         mMode = in.readInt();
         mSourceAddress = in.readString();
         mDestinationAddress = in.readString();
-        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader(), android.net.Network.class);
         mSpiResourceId = in.readInt();
         mEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mAuthentication =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mAuthenticatedEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
         mEncapRemotePort = in.readInt();
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
index 732cf19..390af82 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -81,7 +81,7 @@
         status = in.readInt();
         resourceId = in.readInt();
         port = in.readInt();
-        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
     }
 
     @android.annotation.NonNull
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
index 04d1d68..d3d5a08 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
@@ -16,18 +16,29 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.net.wifi.WifiInfo;
 import android.service.NetworkIdentityProto;
-import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation;
+import android.telephony.TelephonyManager;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkCapabilitiesUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -37,11 +48,24 @@
  *
  * @hide
  */
-public class NetworkIdentity implements Comparable<NetworkIdentity> {
+@SystemApi(client = MODULE_LIBRARIES)
+public class NetworkIdentity {
     private static final String TAG = "NetworkIdentity";
 
+    /** @hide */
+    // TODO: Remove this after migrating all callers to use
+    //  {@link NetworkTemplate#NETWORK_TYPE_ALL} instead.
     public static final int SUBTYPE_COMBINED = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = {
+            NetworkTemplate.OEM_MANAGED_NO,
+            NetworkTemplate.OEM_MANAGED_PAID,
+            NetworkTemplate.OEM_MANAGED_PRIVATE
+    })
+    public @interface OemManaged{}
+
     /**
      * Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}.
      * @hide
@@ -51,29 +75,32 @@
      * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
      * @hide
      */
-    public static final int OEM_PAID = 0x1;
+    public static final int OEM_PAID = 1 << 0;
     /**
      * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
      * @hide
      */
-    public static final int OEM_PRIVATE = 0x2;
+    public static final int OEM_PRIVATE = 1 << 1;
+
+    private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE;
 
     final int mType;
-    final int mSubType;
+    final int mRatType;
     final String mSubscriberId;
-    final String mNetworkId;
+    final String mWifiNetworkKey;
     final boolean mRoaming;
     final boolean mMetered;
     final boolean mDefaultNetwork;
     final int mOemManaged;
 
+    /** @hide */
     public NetworkIdentity(
-            int type, int subType, String subscriberId, String networkId, boolean roaming,
-            boolean metered, boolean defaultNetwork, int oemManaged) {
+            int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
+            boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged) {
         mType = type;
-        mSubType = subType;
+        mRatType = ratType;
         mSubscriberId = subscriberId;
-        mNetworkId = networkId;
+        mWifiNetworkKey = wifiNetworkKey;
         mRoaming = roaming;
         mMetered = metered;
         mDefaultNetwork = defaultNetwork;
@@ -82,7 +109,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mSubType, mSubscriberId, mNetworkId, mRoaming, mMetered,
+        return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
                 mDefaultNetwork, mOemManaged);
     }
 
@@ -90,9 +117,9 @@
     public boolean equals(@Nullable Object obj) {
         if (obj instanceof NetworkIdentity) {
             final NetworkIdentity ident = (NetworkIdentity) obj;
-            return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
+            return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming
                     && Objects.equals(mSubscriberId, ident.mSubscriberId)
-                    && Objects.equals(mNetworkId, ident.mNetworkId)
+                    && Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
                     && mMetered == ident.mMetered
                     && mDefaultNetwork == ident.mDefaultNetwork
                     && mOemManaged == ident.mOemManaged;
@@ -104,18 +131,18 @@
     public String toString() {
         final StringBuilder builder = new StringBuilder("{");
         builder.append("type=").append(mType);
-        builder.append(", subType=");
-        if (mSubType == SUBTYPE_COMBINED) {
+        builder.append(", ratType=");
+        if (mRatType == NETWORK_TYPE_ALL) {
             builder.append("COMBINED");
         } else {
-            builder.append(mSubType);
+            builder.append(mRatType);
         }
         if (mSubscriberId != null) {
             builder.append(", subscriberId=")
                     .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
         }
-        if (mNetworkId != null) {
-            builder.append(", networkId=").append(mNetworkId);
+        if (mWifiNetworkKey != null) {
+            builder.append(", wifiNetworkKey=").append(mWifiNetworkKey);
         }
         if (mRoaming) {
             builder.append(", ROAMING");
@@ -153,12 +180,13 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
         proto.write(NetworkIdentityProto.TYPE, mType);
 
-        // Not dumping mSubType, subtypes are no longer supported.
+        // TODO: dump mRatType as well.
 
         proto.write(NetworkIdentityProto.ROAMING, mRoaming);
         proto.write(NetworkIdentityProto.METERED, mMetered);
@@ -168,77 +196,99 @@
         proto.end(start);
     }
 
+    /** Get the network type of this instance. */
     public int getType() {
         return mType;
     }
 
-    public int getSubType() {
-        return mSubType;
+    /** Get the Radio Access Technology(RAT) type of this instance. */
+    public int getRatType() {
+        return mRatType;
     }
 
+    /** Get the Subscriber Id of this instance. */
+    @Nullable
     public String getSubscriberId() {
         return mSubscriberId;
     }
 
-    public String getNetworkId() {
-        return mNetworkId;
+    /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */
+    @Nullable
+    public String getWifiNetworkKey() {
+        return mWifiNetworkKey;
     }
 
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getRoaming() {
         return mRoaming;
     }
 
+    /** Return whether this network is roaming. */
+    public boolean isRoaming() {
+        return mRoaming;
+    }
+
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getMetered() {
         return mMetered;
     }
 
+    /** Return whether this network is metered. */
+    public boolean isMetered() {
+        return mMetered;
+    }
+
+    /** @hide */
+    // TODO: Remove this function after all callers are removed.
     public boolean getDefaultNetwork() {
         return mDefaultNetwork;
     }
 
+    /** Return whether this network is the default network. */
+    public boolean isDefaultNetwork() {
+        return mDefaultNetwork;
+    }
+
+    /** Get the OEM managed type of this instance. */
     public int getOemManaged() {
         return mOemManaged;
     }
 
     /**
-     * Build a {@link NetworkIdentity} from the given {@link NetworkStateSnapshot} and
-     * {@code subType}, assuming that any mobile networks are using the current IMSI.
-     * The subType if applicable, should be set as one of the TelephonyManager.NETWORK_TYPE_*
-     * constants, or {@link android.telephony.TelephonyManager#NETWORK_TYPE_UNKNOWN} if not.
+     * Assemble a {@link NetworkIdentity} from the passed arguments.
+     *
+     * This methods builds an identity based on the capabilities of the network in the
+     * snapshot and other passed arguments. The identity is used as a key to record data usage.
+     *
+     * @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}.
+     * @param defaultNetwork whether the network is a default network.
+     * @param ratType the Radio Access Technology(RAT) type of the network. Or
+     *                {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable.
+     *                See {@code TelephonyManager.NETWORK_TYPE_*}.
+     * @hide
+     * @deprecated See {@link NetworkIdentity.Builder}.
      */
+    // TODO: Remove this after all callers are migrated to use new Api.
+    @Deprecated
+    @NonNull
     public static NetworkIdentity buildNetworkIdentity(Context context,
-            NetworkStateSnapshot snapshot, boolean defaultNetwork, @NetworkType int subType) {
-        final int legacyType = snapshot.getLegacyType();
-
-        final String subscriberId = snapshot.getSubscriberId();
-        String networkId = null;
-        boolean roaming = !snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        boolean metered = !(snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                || snapshot.getNetworkCapabilities().hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED));
-
-        final int oemManaged = getOemBitfield(snapshot.getNetworkCapabilities());
-
-        if (legacyType == TYPE_WIFI) {
-            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
-                    .getTransportInfo();
-            if (transportInfo instanceof WifiInfo) {
-                final WifiInfo info = (WifiInfo) transportInfo;
-                networkId = info != null ? info.getCurrentNetworkKey() : null;
-            }
+            @NonNull NetworkStateSnapshot snapshot,
+            boolean defaultNetwork, @Annotation.NetworkType int ratType) {
+        final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
+                .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork);
+        if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
+            builder.setRatType(ratType);
         }
-
-        return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered,
-                defaultNetwork, oemManaged);
+        return builder.build();
     }
 
     /**
      * Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}.
      * @hide
      */
-    public static int getOemBitfield(NetworkCapabilities nc) {
+    public static int getOemBitfield(@NonNull NetworkCapabilities nc) {
         int oemManaged = OEM_NONE;
 
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
@@ -251,30 +301,265 @@
         return oemManaged;
     }
 
-    @Override
-    public int compareTo(NetworkIdentity another) {
-        int res = Integer.compare(mType, another.mType);
+    /** @hide */
+    public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) {
+        Objects.requireNonNull(right);
+        int res = Integer.compare(left.mType, right.mType);
         if (res == 0) {
-            res = Integer.compare(mSubType, another.mSubType);
+            res = Integer.compare(left.mRatType, right.mRatType);
         }
-        if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) {
-            res = mSubscriberId.compareTo(another.mSubscriberId);
+        if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) {
+            res = left.mSubscriberId.compareTo(right.mSubscriberId);
         }
-        if (res == 0 && mNetworkId != null && another.mNetworkId != null) {
-            res = mNetworkId.compareTo(another.mNetworkId);
+        if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) {
+            res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey);
         }
         if (res == 0) {
-            res = Boolean.compare(mRoaming, another.mRoaming);
+            res = Boolean.compare(left.mRoaming, right.mRoaming);
         }
         if (res == 0) {
-            res = Boolean.compare(mMetered, another.mMetered);
+            res = Boolean.compare(left.mMetered, right.mMetered);
         }
         if (res == 0) {
-            res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork);
+            res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork);
         }
         if (res == 0) {
-            res = Integer.compare(mOemManaged, another.mOemManaged);
+            res = Integer.compare(left.mOemManaged, right.mOemManaged);
         }
         return res;
     }
+
+    /**
+     * Builder class for {@link NetworkIdentity}.
+     */
+    public static final class Builder {
+        // Need to be synchronized with ConnectivityManager.
+        // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module.
+        private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST
+        private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
+        private int mType;
+        private int mRatType;
+        private String mSubscriberId;
+        private String mWifiNetworkKey;
+        private boolean mRoaming;
+        private boolean mMetered;
+        private boolean mDefaultNetwork;
+        private int mOemManaged;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+            // Initialize with default values. Will be overwritten by setters.
+            mType = ConnectivityManager.TYPE_NONE;
+            mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+            mSubscriberId = null;
+            mWifiNetworkKey = null;
+            mRoaming = false;
+            mMetered = false;
+            mDefaultNetwork = false;
+            mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
+        }
+
+        /**
+         * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance.
+         * This is a useful shorthand that will read from the snapshot and set the
+         * following fields, if they are set in the snapshot :
+         *  - type
+         *  - subscriberId
+         *  - roaming
+         *  - metered
+         *  - oemManaged
+         *  - wifiNetworkKey
+         *
+         * @param snapshot The target {@link NetworkStateSnapshot} object.
+         * @return The builder object.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) {
+            setType(snapshot.getLegacyType());
+
+            setSubscriberId(snapshot.getSubscriberId());
+            setRoaming(!snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
+            setMetered(!(snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                    || snapshot.getNetworkCapabilities().hasCapability(
+                    NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)));
+
+            setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
+
+            if (mType == TYPE_WIFI) {
+                final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+                        .getTransportInfo();
+                if (transportInfo instanceof WifiInfo) {
+                    final WifiInfo info = (WifiInfo) transportInfo;
+                    setWifiNetworkKey(info.getNetworkKey());
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Set the network type of the network.
+         *
+         * @param type the network type. See {@link ConnectivityManager#TYPE_*}.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setType(int type) {
+            // Include TYPE_NONE for compatibility, type field might not be filled by some
+            // networks such as test networks.
+            if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type)
+                    && type != ConnectivityManager.TYPE_NONE) {
+                throw new IllegalArgumentException("Invalid network type: " + type);
+            }
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Set the Radio Access Technology(RAT) type of the network.
+         *
+         * No RAT type is specified by default. Call clearRatType to reset.
+         *
+         * @param ratType the Radio Access Technology(RAT) type if applicable. See
+         *                {@code TelephonyManager.NETWORK_TYPE_*}.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setRatType(@Annotation.NetworkType int ratType) {
+            if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
+                    && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                throw new IllegalArgumentException("Invalid ratType " + ratType);
+            }
+            mRatType = ratType;
+            return this;
+        }
+
+        /**
+         * Clear the Radio Access Technology(RAT) type of the network.
+         *
+         * @return this builder.
+         */
+        @NonNull
+        public Builder clearRatType() {
+            mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
+            return this;
+        }
+
+        /**
+         * Set the Subscriber Id.
+         *
+         * @param subscriberId the Subscriber Id of the network. Or null if not applicable.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setSubscriberId(@Nullable String subscriberId) {
+            mSubscriberId = subscriberId;
+            return this;
+        }
+
+        /**
+         * Set the Wifi Network Key.
+         *
+         * @param wifiNetworkKey Wifi Network Key of the network,
+         *                        see {@link WifiInfo#getNetworkKey()}.
+         *                        Or null if not applicable.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
+            mWifiNetworkKey = wifiNetworkKey;
+            return this;
+        }
+
+        /**
+         * Set whether this network is roaming.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param roaming the roaming status of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setRoaming(boolean roaming) {
+            mRoaming = roaming;
+            return this;
+        }
+
+        /**
+         * Set whether this network is metered.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param metered the meteredness of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setMetered(boolean metered) {
+            mMetered = metered;
+            return this;
+        }
+
+        /**
+         * Set whether this network is the default network.
+         *
+         * This field is false by default. Call with false to reset.
+         *
+         * @param defaultNetwork the default network status of the network.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setDefaultNetwork(boolean defaultNetwork) {
+            mDefaultNetwork = defaultNetwork;
+            return this;
+        }
+
+        /**
+         * Set the OEM managed type.
+         *
+         * @param oemManaged Type of OEM managed network or unmanaged networks.
+         *                   See {@code NetworkTemplate#OEM_MANAGED_*}.
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setOemManaged(@OemManaged int oemManaged) {
+            // Assert input does not contain illegal oemManage bits.
+            if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) {
+                throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged);
+            }
+            mOemManaged = oemManaged;
+            return this;
+        }
+
+        private void ensureValidParameters() {
+            // Assert non-mobile network cannot have a ratType.
+            if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
+                throw new IllegalArgumentException(
+                        "Invalid ratType " + mRatType + " for type " + mType);
+            }
+
+            // Assert non-wifi network cannot have a wifi network key.
+            if (mType != TYPE_WIFI && mWifiNetworkKey != null) {
+                throw new IllegalArgumentException("Invalid wifi network key for type " + mType);
+            }
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkIdentity}.
+         *
+         * @return the built instance of {@link NetworkIdentity}.
+         */
+        @NonNull
+        public NetworkIdentity build() {
+            ensureValidParameters();
+            return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
+                    mRoaming, mMetered, mDefaultNetwork, mOemManaged);
+        }
+    }
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
index abbebef..dfa347f 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
@@ -18,6 +18,7 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 
+import android.annotation.NonNull;
 import android.service.NetworkIdentitySetProto;
 import android.util.proto.ProtoOutputStream;
 
@@ -25,6 +26,8 @@
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
@@ -32,8 +35,7 @@
  *
  * @hide
  */
-public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements
-        Comparable<NetworkIdentitySet> {
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_ROAMING = 2;
     private static final int VERSION_ADD_NETWORK_ID = 3;
@@ -41,9 +43,19 @@
     private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
     private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
 
+    /**
+     * Construct a {@link NetworkIdentitySet} object.
+     */
     public NetworkIdentitySet() {
+        super();
     }
 
+    /** @hide */
+    public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) {
+        super(ident);
+    }
+
+    /** @hide */
     public NetworkIdentitySet(DataInput in) throws IOException {
         final int version = in.readInt();
         final int size = in.readInt();
@@ -52,7 +64,7 @@
                 final int ignored = in.readInt();
             }
             final int type = in.readInt();
-            final int subType = in.readInt();
+            final int ratType = in.readInt();
             final String subscriberId = readOptionalString(in);
             final String networkId;
             if (version >= VERSION_ADD_NETWORK_ID) {
@@ -91,63 +103,73 @@
                 oemNetCapabilities = NetworkIdentity.OEM_NONE;
             }
 
-            add(new NetworkIdentity(type, subType, subscriberId, networkId, roaming, metered,
+            add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
                     defaultNetwork, oemNetCapabilities));
         }
     }
 
     /**
      * Method to serialize this object into a {@code DataOutput}.
+     * @hide
      */
     public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK);
         out.writeInt(size());
         for (NetworkIdentity ident : this) {
             out.writeInt(ident.getType());
-            out.writeInt(ident.getSubType());
+            out.writeInt(ident.getRatType());
             writeOptionalString(out, ident.getSubscriberId());
-            writeOptionalString(out, ident.getNetworkId());
-            out.writeBoolean(ident.getRoaming());
-            out.writeBoolean(ident.getMetered());
-            out.writeBoolean(ident.getDefaultNetwork());
+            writeOptionalString(out, ident.getWifiNetworkKey());
+            out.writeBoolean(ident.isRoaming());
+            out.writeBoolean(ident.isMetered());
+            out.writeBoolean(ident.isDefaultNetwork());
             out.writeInt(ident.getOemManaged());
         }
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered metered. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered metered.
+     * @hide
+     */
     public boolean isAnyMemberMetered() {
         if (isEmpty()) {
             return false;
         }
         for (NetworkIdentity ident : this) {
-            if (ident.getMetered()) {
+            if (ident.isMetered()) {
                 return true;
             }
         }
         return false;
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered roaming. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered roaming.
+     * @hide
+     */
     public boolean isAnyMemberRoaming() {
         if (isEmpty()) {
             return false;
         }
         for (NetworkIdentity ident : this) {
-            if (ident.getRoaming()) {
+            if (ident.isRoaming()) {
                 return true;
             }
         }
         return false;
     }
 
-    /** @return whether any {@link NetworkIdentity} in this set is considered on the default
-            network. */
+    /**
+     * @return whether any {@link NetworkIdentity} in this set is considered on the default
+     *         network.
+     * @hide
+     */
     public boolean areAllMembersOnDefaultNetwork() {
         if (isEmpty()) {
             return true;
         }
         for (NetworkIdentity ident : this) {
-            if (!ident.getDefaultNetwork()) {
+            if (!ident.isDefaultNetwork()) {
                 return false;
             }
         }
@@ -171,18 +193,20 @@
         }
     }
 
-    @Override
-    public int compareTo(NetworkIdentitySet another) {
-        if (isEmpty()) return -1;
-        if (another.isEmpty()) return 1;
+    public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
+        Objects.requireNonNull(left);
+        Objects.requireNonNull(right);
+        if (left.isEmpty()) return -1;
+        if (right.isEmpty()) return 1;
 
-        final NetworkIdentity ident = iterator().next();
-        final NetworkIdentity anotherIdent = another.iterator().next();
-        return ident.compareTo(anotherIdent);
+        final NetworkIdentity leftIdent = left.iterator().next();
+        final NetworkIdentity rightIdent = right.iterator().next();
+        return NetworkIdentity.compare(leftIdent, rightIdent);
     }
 
     /**
      * Method to dump this object into proto debug file.
+     * @hide
      */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
index 3915634..d577aa8 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -73,9 +73,9 @@
 
     /** @hide */
     public NetworkStateSnapshot(@NonNull Parcel in) {
-        mNetwork = in.readParcelable(null);
-        mNetworkCapabilities = in.readParcelable(null);
-        mLinkProperties = in.readParcelable(null);
+        mNetwork = in.readParcelable(null, android.net.Network.class);
+        mNetworkCapabilities = in.readParcelable(null, android.net.NetworkCapabilities.class);
+        mLinkProperties = in.readParcelable(null, android.net.LinkProperties.class);
         mSubscriberId = in.readString();
         mLegacyType = in.readInt();
     }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index 9d532e7..9175809 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -41,6 +41,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -57,7 +58,7 @@
  */
 // @NotThreadSafe
 @SystemApi
-public final class NetworkStats implements Parcelable {
+public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Entry> {
     private static final String TAG = "NetworkStats";
 
     /**
@@ -678,6 +679,35 @@
     }
 
     /**
+     * Iterate over Entry objects.
+     *
+     * Return an iterator of this object that will iterate through all contained Entry objects.
+     *
+     * This iterator does not support concurrent modification and makes no guarantee of fail-fast
+     * behavior. If any method that can mutate the contents of this object is called while
+     * iteration is in progress, either inside the loop or in another thread, then behavior is
+     * undefined.
+     * The remove() method is not implemented and will throw UnsupportedOperationException.
+     * @hide
+     */
+    @SystemApi
+    @NonNull public Iterator<Entry> iterator() {
+        return new Iterator<Entry>() {
+            int mIndex = 0;
+
+            @Override
+            public boolean hasNext() {
+                return mIndex < size;
+            }
+
+            @Override
+            public Entry next() {
+                return getValues(mIndex++, null);
+            }
+        };
+    }
+
+    /**
      * Return specific stats entry.
      * @hide
      */
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index 9f9d73f..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;
@@ -32,6 +33,10 @@
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
 
+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;
@@ -69,7 +74,10 @@
 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;
 
 /**
  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
@@ -77,6 +85,7 @@
  *
  * @hide
  */
+@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" */
@@ -100,15 +109,23 @@
     private long mTotalBytes;
     private boolean mDirty;
 
+    /**
+     * Construct a {@link NetworkStatsCollection} object.
+     *
+     * @param bucketDuration duration of the buckets in this object, in milliseconds.
+     * @hide
+     */
     public NetworkStatsCollection(long bucketDuration) {
         mBucketDuration = bucketDuration;
         reset();
     }
 
+    /** @hide */
     public void clear() {
         reset();
     }
 
+    /** @hide */
     public void reset() {
         mStats.clear();
         mStartMillis = Long.MAX_VALUE;
@@ -117,6 +134,7 @@
         mDirty = false;
     }
 
+    /** @hide */
     public long getStartMillis() {
         return mStartMillis;
     }
@@ -124,6 +142,7 @@
     /**
      * Return first atomic bucket in this collection, which is more conservative
      * than {@link #mStartMillis}.
+     * @hide
      */
     public long getFirstAtomicBucketMillis() {
         if (mStartMillis == Long.MAX_VALUE) {
@@ -133,26 +152,32 @@
         }
     }
 
+    /** @hide */
     public long getEndMillis() {
         return mEndMillis;
     }
 
+    /** @hide */
     public long getTotalBytes() {
         return mTotalBytes;
     }
 
+    /** @hide */
     public boolean isDirty() {
         return mDirty;
     }
 
+    /** @hide */
     public void clearDirty() {
         mDirty = false;
     }
 
+    /** @hide */
     public boolean isEmpty() {
         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
     }
 
+    /** @hide */
     @VisibleForTesting
     public long roundUp(long time) {
         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
@@ -168,6 +193,7 @@
         }
     }
 
+    /** @hide */
     @VisibleForTesting
     public long roundDown(long time) {
         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
@@ -182,10 +208,12 @@
         }
     }
 
+    /** @hide */
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
         return getRelevantUids(accessLevel, Binder.getCallingUid());
     }
 
+    /** @hide */
     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
                 final int callerUid) {
         final ArrayList<Integer> uids = new ArrayList<>();
@@ -206,6 +234,7 @@
     /**
      * Combine all {@link NetworkStatsHistory} in this collection which match
      * the requested parameters.
+     * @hide
      */
     public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
             int uid, int set, int tag, int fields, long start, long end,
@@ -331,6 +360,7 @@
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param accessLevel - caller access level.
      * @param callerUid - caller UID.
+     * @hide
      */
     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
@@ -377,6 +407,7 @@
 
     /**
      * Record given {@link android.net.NetworkStats.Entry} into this collection.
+     * @hide
      */
     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
             long end, NetworkStats.Entry entry) {
@@ -387,8 +418,12 @@
 
     /**
      * Record given {@link NetworkStatsHistory} into this collection.
+     *
+     * @hide
      */
-    private void recordHistory(Key key, NetworkStatsHistory history) {
+    public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) {
+        Objects.requireNonNull(key);
+        Objects.requireNonNull(history);
         if (history.size() == 0) return;
         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
 
@@ -403,8 +438,11 @@
     /**
      * Record all {@link NetworkStatsHistory} contained in the given collection
      * into this collection.
+     *
+     * @hide
      */
-    public void recordCollection(NetworkStatsCollection another) {
+    public void recordCollection(@NonNull NetworkStatsCollection another) {
+        Objects.requireNonNull(another);
         for (int i = 0; i < another.mStats.size(); i++) {
             final Key key = another.mStats.keyAt(i);
             final NetworkStatsHistory value = another.mStats.valueAt(i);
@@ -433,6 +471,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void read(InputStream in) throws IOException {
         read((DataInput) new DataInputStream(in));
@@ -472,6 +511,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public void write(OutputStream out) throws IOException {
         write((DataOutput) new DataOutputStream(out));
@@ -514,6 +554,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyNetwork(File file) throws IOException {
@@ -559,6 +600,7 @@
      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
      *
      * @deprecated
+     * @hide
      */
     @Deprecated
     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
@@ -629,6 +671,7 @@
      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
      * moving any {@link NetworkStats#TAG_NONE} series to
      * {@link TrafficStats#UID_REMOVED}.
+     * @hide
      */
     public void removeUids(int[] uids) {
         final ArrayList<Key> knownKeys = new ArrayList<>();
@@ -665,10 +708,11 @@
     private ArrayList<Key> getSortedKeys() {
         final ArrayList<Key> keys = new ArrayList<>();
         keys.addAll(mStats.keySet());
-        Collections.sort(keys);
+        Collections.sort(keys, (left, right) -> Key.compare(left, right));
         return keys;
     }
 
+    /** @hide */
     public void dump(IndentingPrintWriter pw) {
         for (Key key : getSortedKeys()) {
             pw.print("ident="); pw.print(key.ident.toString());
@@ -683,6 +727,7 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
@@ -706,6 +751,7 @@
         proto.end(start);
     }
 
+    /** @hide */
     public void dumpCheckin(PrintWriter pw, long start, long end) {
         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
@@ -768,16 +814,102 @@
         return false;
     }
 
-    private static class Key implements Comparable<Key> {
+    /**
+     * 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.
+     */
+    public static class Key {
+        /** @hide */
         public final NetworkIdentitySet ident;
+        /** @hide */
         public final int uid;
+        /** @hide */
         public final int set;
+        /** @hide */
         public final int tag;
 
         private final int mHashCode;
 
-        Key(NetworkIdentitySet ident, int uid, int set, int tag) {
-            this.ident = ident;
+        /**
+         * Construct a {@link Key} object.
+         *
+         * @param ident a Set of {@link NetworkIdentity} that associated with the record.
+         * @param uid Uid of the record.
+         * @param set Set of the record, see {@code NetworkStats#SET_*}.
+         * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}.
+         */
+        public Key(@NonNull Set<NetworkIdentity> ident, int uid, int set, int tag) {
+            this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag);
+        }
+
+        /** @hide */
+        public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) {
+            this.ident = Objects.requireNonNull(ident);
             this.uid = uid;
             this.set = set;
             this.tag = tag;
@@ -790,7 +922,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (obj instanceof Key) {
                 final Key key = (Key) obj;
                 return uid == key.uid && set == key.set && tag == key.tag
@@ -799,20 +931,22 @@
             return false;
         }
 
-        @Override
-        public int compareTo(Key another) {
+        /** @hide */
+        public static int compare(@NonNull Key left, @NonNull Key right) {
+            Objects.requireNonNull(left);
+            Objects.requireNonNull(right);
             int res = 0;
-            if (ident != null && another.ident != null) {
-                res = ident.compareTo(another.ident);
+            if (left.ident != null && right.ident != null) {
+                res = NetworkIdentitySet.compare(left.ident, right.ident);
             }
             if (res == 0) {
-                res = Integer.compare(uid, another.uid);
+                res = Integer.compare(left.uid, right.uid);
             }
             if (res == 0) {
-                res = Integer.compare(set, another.set);
+                res = Integer.compare(left.set, right.set);
             }
             if (res == 0) {
-                res = Integer.compare(tag, another.tag);
+                res = Integer.compare(left.tag, right.tag);
             }
             return res;
         }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
index 428bc6d..78c1370 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
@@ -30,6 +31,8 @@
 
 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -50,7 +53,9 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -64,18 +69,25 @@
  *
  * @hide
  */
-public class NetworkStatsHistory implements Parcelable {
+@SystemApi(client = MODULE_LIBRARIES)
+public final class NetworkStatsHistory implements Parcelable {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_PACKETS = 2;
     private static final int VERSION_ADD_ACTIVE = 3;
 
+    /** @hide */
     public static final int FIELD_ACTIVE_TIME = 0x01;
+    /** @hide */
     public static final int FIELD_RX_BYTES = 0x02;
+    /** @hide */
     public static final int FIELD_RX_PACKETS = 0x04;
+    /** @hide */
     public static final int FIELD_TX_BYTES = 0x08;
+    /** @hide */
     public static final int FIELD_TX_PACKETS = 0x10;
+    /** @hide */
     public static final int FIELD_OPERATIONS = 0x20;
-
+    /** @hide */
     public static final int FIELD_ALL = 0xFFFFFFFF;
 
     private long bucketDuration;
@@ -89,34 +101,171 @@
     private long[] operations;
     private long totalBytes;
 
-    public static class Entry {
-        public static final long UNKNOWN = -1;
-
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long bucketDuration;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long bucketStart;
-        public long activeTime;
-        @UnsupportedAppUsage
-        public long rxBytes;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long rxPackets;
-        @UnsupportedAppUsage
-        public long txBytes;
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        public long txPackets;
-        public long operations;
+    /** @hide */
+    public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime,
+            long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets,
+            long[] operations, int bucketCount, long totalBytes) {
+        this.bucketDuration = bucketDuration;
+        this.bucketStart = bucketStart;
+        this.activeTime = activeTime;
+        this.rxBytes = rxBytes;
+        this.rxPackets = rxPackets;
+        this.txBytes = txBytes;
+        this.txPackets = txPackets;
+        this.operations = operations;
+        this.bucketCount = bucketCount;
+        this.totalBytes = totalBytes;
     }
 
+    /**
+     * An instance to represent a single record in a {@link NetworkStatsHistory} object.
+     */
+    public static final class Entry {
+        /** @hide */
+        public static final long UNKNOWN = -1;
+
+        /** @hide */
+        // TODO: Migrate all callers to get duration from the history object and remove this field.
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long bucketDuration;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long bucketStart;
+        /** @hide */
+        public long activeTime;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long rxBytes;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long rxPackets;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long txBytes;
+        /** @hide */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        public long txPackets;
+        /** @hide */
+        public long operations;
+        /** @hide */
+        Entry() {}
+
+        /**
+         * Construct a {@link Entry} instance to represent a single record in a
+         * {@link NetworkStatsHistory} object.
+         *
+         * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the
+         *                    Unix epoch, see {@link java.lang.System#currentTimeMillis}.
+         * @param activeTime Active time for this {@link Entry}, in milliseconds.
+         * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
+         *                represent the contents of IP packets, including IP headers.
+         * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
+         *                  represent the contents of IP packets, including IP headers.
+         * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
+         *                represent the contents of IP packets, including IP headers.
+         * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
+         *                  represent the contents of IP packets, including IP headers.
+         * @param operations count of network operations performed for this {@link Entry}. This can
+         *                   be used to derive bytes-per-operation.
+         */
+        public Entry(long bucketStart, long activeTime, long rxBytes,
+                long rxPackets, long txBytes, long txPackets, long operations) {
+            this.bucketStart = bucketStart;
+            this.activeTime = activeTime;
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+            this.operations = operations;
+        }
+
+        /**
+         * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch.
+         */
+        public long getBucketStart() {
+            return bucketStart;
+        }
+
+        /**
+         * Get active time of the bucket's time interval, in milliseconds.
+         */
+        public long getActiveTime() {
+            return activeTime;
+        }
+
+        /** Get number of bytes received for this {@link Entry}. */
+        public long getRxBytes() {
+            return rxBytes;
+        }
+
+        /** Get number of packets received for this {@link Entry}. */
+        public long getRxPackets() {
+            return rxPackets;
+        }
+
+        /** Get number of bytes transmitted for this {@link Entry}. */
+        public long getTxBytes() {
+            return txBytes;
+        }
+
+        /** Get number of packets transmitted for this {@link Entry}. */
+        public long getTxPackets() {
+            return txPackets;
+        }
+
+        /** Get count of network operations performed for this {@link Entry}. */
+        public long getOperations() {
+            return operations;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+            Entry entry = (Entry) o;
+            return bucketStart == entry.bucketStart
+                    && activeTime == entry.activeTime && rxBytes == entry.rxBytes
+                    && rxPackets == entry.rxPackets && txBytes == entry.txBytes
+                    && txPackets == entry.txPackets && operations == entry.operations;
+        }
+
+        @Override
+        public int hashCode() {
+            return (int) (bucketStart * 2
+                    + activeTime * 3
+                    + rxBytes * 5
+                    + rxPackets * 7
+                    + txBytes * 11
+                    + txPackets * 13
+                    + operations * 17);
+        }
+
+        @Override
+        public String toString() {
+            return "Entry{"
+                    + "bucketStart=" + bucketStart
+                    + ", activeTime=" + activeTime
+                    + ", rxBytes=" + rxBytes
+                    + ", rxPackets=" + rxPackets
+                    + ", txBytes=" + txBytes
+                    + ", txPackets=" + txPackets
+                    + ", operations=" + operations
+                    + "}";
+        }
+    }
+
+    /** @hide */
     @UnsupportedAppUsage
     public NetworkStatsHistory(long bucketDuration) {
         this(bucketDuration, 10, FIELD_ALL);
     }
 
+    /** @hide */
     public NetworkStatsHistory(long bucketDuration, int initialSize) {
         this(bucketDuration, initialSize, FIELD_ALL);
     }
 
+    /** @hide */
     public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
         this.bucketDuration = bucketDuration;
         bucketStart = new long[initialSize];
@@ -130,11 +279,13 @@
         totalBytes = 0;
     }
 
+    /** @hide */
     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
         recordEntireHistory(existing);
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public NetworkStatsHistory(Parcel in) {
         bucketDuration = in.readLong();
@@ -150,7 +301,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeLong(bucketDuration);
         writeLongArray(out, bucketStart, bucketCount);
         writeLongArray(out, activeTime, bucketCount);
@@ -162,6 +313,7 @@
         out.writeLong(totalBytes);
     }
 
+    /** @hide */
     public NetworkStatsHistory(DataInput in) throws IOException {
         final int version = in.readInt();
         switch (version) {
@@ -204,6 +356,7 @@
         }
     }
 
+    /** @hide */
     public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_ACTIVE);
         out.writeLong(bucketDuration);
@@ -221,15 +374,18 @@
         return 0;
     }
 
+    /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int size() {
         return bucketCount;
     }
 
+    /** @hide */
     public long getBucketDuration() {
         return bucketDuration;
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public long getStart() {
         if (bucketCount > 0) {
@@ -239,6 +395,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public long getEnd() {
         if (bucketCount > 0) {
@@ -250,6 +407,7 @@
 
     /**
      * Return total bytes represented by this history.
+     * @hide
      */
     public long getTotalBytes() {
         return totalBytes;
@@ -258,6 +416,7 @@
     /**
      * Return index of bucket that contains or is immediately before the
      * requested time.
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int getIndexBefore(long time) {
@@ -273,6 +432,7 @@
     /**
      * Return index of bucket that contains or is immediately after the
      * requested time.
+     * @hide
      */
     public int getIndexAfter(long time) {
         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
@@ -286,6 +446,7 @@
 
     /**
      * Return specific stats entry.
+     * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Entry getValues(int i, Entry recycle) {
@@ -301,6 +462,23 @@
         return entry;
     }
 
+    /**
+     * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance.
+     *
+     * @return
+     */
+    @NonNull
+    public List<Entry> getEntries() {
+        // TODO: Return a wrapper that uses this list instead, to prevent the returned result
+        //  from being changed.
+        final ArrayList<Entry> ret = new ArrayList<>(size());
+        for (int i = 0; i < size(); i++) {
+            ret.add(getValues(i, null /* recycle */));
+        }
+        return ret;
+    }
+
+    /** @hide */
     public void setValues(int i, Entry entry) {
         // Unwind old values
         if (rxBytes != null) totalBytes -= rxBytes[i];
@@ -322,6 +500,7 @@
     /**
      * Record that data traffic occurred in the given time range. Will
      * distribute across internal buckets, creating new buckets as needed.
+     * @hide
      */
     @Deprecated
     public void recordData(long start, long end, long rxBytes, long txBytes) {
@@ -332,6 +511,7 @@
     /**
      * Record that data traffic occurred in the given time range. Will
      * distribute across internal buckets, creating new buckets as needed.
+     * @hide
      */
     public void recordData(long start, long end, NetworkStats.Entry entry) {
         long rxBytes = entry.rxBytes;
@@ -392,6 +572,7 @@
     /**
      * Record an entire {@link NetworkStatsHistory} into this history. Usually
      * for combining together stats for external reporting.
+     * @hide
      */
     @UnsupportedAppUsage
     public void recordEntireHistory(NetworkStatsHistory input) {
@@ -402,6 +583,7 @@
      * Record given {@link NetworkStatsHistory} into this history, copying only
      * buckets that atomically occur in the inclusive time range. Doesn't
      * interpolate across partial buckets.
+     * @hide
      */
     public void recordHistory(NetworkStatsHistory input, long start, long end) {
         final NetworkStats.Entry entry = new NetworkStats.Entry(
@@ -483,6 +665,7 @@
 
     /**
      * Clear all data stored in this object.
+     * @hide
      */
     public void clear() {
         bucketStart = EmptyArray.LONG;
@@ -498,9 +681,10 @@
 
     /**
      * Remove buckets older than requested cutoff.
+     * @hide
      */
-    @Deprecated
     public void removeBucketsBefore(long cutoff) {
+        // TODO: Consider use getIndexBefore.
         int i;
         for (i = 0; i < bucketCount; i++) {
             final long curStart = bucketStart[i];
@@ -522,7 +706,9 @@
             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
             bucketCount -= i;
 
-            // TODO: subtract removed values from totalBytes
+            totalBytes = 0;
+            if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes);
+            if (txBytes != null) totalBytes += CollectionUtils.total(txBytes);
         }
     }
 
@@ -536,6 +722,7 @@
      * @param start - start of the range, timestamp in milliseconds since the epoch.
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param recycle - entry instance for performance, could be null.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, Entry recycle) {
@@ -550,6 +737,7 @@
      * @param end - end of the range, timestamp in milliseconds since the epoch.
      * @param now - current timestamp in milliseconds since the epoch (wall clock).
      * @param recycle - entry instance for performance, could be null.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, long now, Entry recycle) {
@@ -613,6 +801,7 @@
 
     /**
      * @deprecated only for temporary testing
+     * @hide
      */
     @Deprecated
     public void generateRandom(long start, long end, long bytes) {
@@ -631,6 +820,7 @@
 
     /**
      * @deprecated only for temporary testing
+     * @hide
      */
     @Deprecated
     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
@@ -660,12 +850,14 @@
         }
     }
 
+    /** @hide */
     public static long randomLong(Random r, long start, long end) {
         return (long) (start + (r.nextFloat() * (end - start)));
     }
 
     /**
      * Quickly determine if this history intersects with given window.
+     * @hide
      */
     public boolean intersects(long start, long end) {
         final long dataStart = getStart();
@@ -677,6 +869,7 @@
         return false;
     }
 
+    /** @hide */
     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
         pw.print("NetworkStatsHistory: bucketDuration=");
         pw.println(bucketDuration / SECOND_IN_MILLIS);
@@ -700,6 +893,7 @@
         pw.decreaseIndent();
     }
 
+    /** @hide */
     public void dumpCheckin(PrintWriter pw) {
         pw.print("d,");
         pw.print(bucketDuration / SECOND_IN_MILLIS);
@@ -717,6 +911,7 @@
         }
     }
 
+    /** @hide */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
@@ -776,6 +971,7 @@
         if (array != null) array[i] += value;
     }
 
+    /** @hide */
     public int estimateResizeBuckets(long newBucketDuration) {
         return (int) (size() * getBucketDuration() / newBucketDuration);
     }
@@ -783,6 +979,7 @@
     /**
      * Utility methods for interacting with {@link DataInputStream} and
      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
+     * @hide
      */
     public static class DataStreamUtils {
         @Deprecated
@@ -857,6 +1054,7 @@
     /**
      * Utility methods for interacting with {@link Parcel} structures, mostly
      * dealing with writing partial arrays.
+     * @hide
      */
     public static class ParcelUtils {
         public static long[] readLongArray(Parcel in) {
@@ -884,4 +1082,80 @@
         }
     }
 
+    /**
+     * Builder class for {@link NetworkStatsHistory}.
+     */
+    public static final class Builder {
+        private final long mBucketDuration;
+        private final List<Long> mBucketStart;
+        private final List<Long> mActiveTime;
+        private final List<Long> mRxBytes;
+        private final List<Long> mRxPackets;
+        private final List<Long> mTxBytes;
+        private final List<Long> mTxPackets;
+        private final List<Long> mOperations;
+
+        /**
+         * Creates a new Builder with given bucket duration and initial capacity to construct
+         * {@link NetworkStatsHistory} objects.
+         *
+         * @param bucketDuration Duration of the buckets of the object, in milliseconds.
+         * @param initialCapacity Estimated number of records.
+         */
+        public Builder(long bucketDuration, int initialCapacity) {
+            mBucketDuration = bucketDuration;
+            mBucketStart = new ArrayList<>(initialCapacity);
+            mActiveTime = new ArrayList<>(initialCapacity);
+            mRxBytes = new ArrayList<>(initialCapacity);
+            mRxPackets = new ArrayList<>(initialCapacity);
+            mTxBytes = new ArrayList<>(initialCapacity);
+            mTxPackets = new ArrayList<>(initialCapacity);
+            mOperations = new ArrayList<>(initialCapacity);
+        }
+
+        /**
+         * Add an {@link Entry} into the {@link NetworkStatsHistory} instance.
+         *
+         * @param entry The target {@link Entry} object.
+         * @return The builder object.
+         */
+        @NonNull
+        public Builder addEntry(@NonNull Entry entry) {
+            mBucketStart.add(entry.bucketStart);
+            mActiveTime.add(entry.activeTime);
+            mRxBytes.add(entry.rxBytes);
+            mRxPackets.add(entry.rxPackets);
+            mTxBytes.add(entry.txBytes);
+            mTxPackets.add(entry.txPackets);
+            mOperations.add(entry.operations);
+            return this;
+        }
+
+        private static long sum(@NonNull List<Long> list) {
+            long sum = 0;
+            for (long entry : list) {
+                sum += entry;
+            }
+            return sum;
+        }
+
+        /**
+         * Builds the instance of the {@link NetworkStatsHistory}.
+         *
+         * @return the built instance of {@link NetworkStatsHistory}.
+         */
+        @NonNull
+        public NetworkStatsHistory build() {
+            return new NetworkStatsHistory(mBucketDuration,
+                    CollectionUtils.toLongArray(mBucketStart),
+                    CollectionUtils.toLongArray(mActiveTime),
+                    CollectionUtils.toLongArray(mRxBytes),
+                    CollectionUtils.toLongArray(mRxPackets),
+                    CollectionUtils.toLongArray(mTxBytes),
+                    CollectionUtils.toLongArray(mTxPackets),
+                    CollectionUtils.toLongArray(mOperations),
+                    mBucketStart.size(),
+                    sum(mRxBytes) + sum(mTxBytes));
+        }
+    }
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index e9084b0..cad8075 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -263,7 +263,7 @@
      * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
      * given key of the wifi network.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      * @hide
      */
@@ -283,7 +283,7 @@
      * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
      * of key of the wifi network.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      * @param subscriberId the IMSI associated to this wifi network.
      *
@@ -364,7 +364,7 @@
     private final int mMetered;
     private final int mRoaming;
     private final int mDefaultNetwork;
-    private final int mSubType;
+    private final int mRatType;
     /**
      * The subscriber Id match rule defines how the template should match networks with
      * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
@@ -413,18 +413,18 @@
     /** @hide */
     // TODO: Remove it after updating all of the caller.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
-            String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int subType,
+            String wifiNetworkKey, int metered, int roaming, int defaultNetwork, int ratType,
             int oemManaged) {
         this(matchRule, subscriberId, matchSubscriberIds,
                 wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
-                metered, roaming, defaultNetwork, subType, oemManaged,
+                metered, roaming, defaultNetwork, ratType, oemManaged,
                 NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
     }
 
     /** @hide */
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String[] matchWifiNetworkKeys, int metered, int roaming,
-            int defaultNetwork, int subType, int oemManaged, int subscriberIdMatchRule) {
+            int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
         Objects.requireNonNull(matchWifiNetworkKeys);
         mMatchRule = matchRule;
         mSubscriberId = subscriberId;
@@ -435,7 +435,7 @@
         mMetered = metered;
         mRoaming = roaming;
         mDefaultNetwork = defaultNetwork;
-        mSubType = subType;
+        mRatType = ratType;
         mOemManaged = oemManaged;
         mSubscriberIdMatchRule = subscriberIdMatchRule;
         checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
@@ -453,7 +453,7 @@
         mMetered = in.readInt();
         mRoaming = in.readInt();
         mDefaultNetwork = in.readInt();
-        mSubType = in.readInt();
+        mRatType = in.readInt();
         mOemManaged = in.readInt();
         mSubscriberIdMatchRule = in.readInt();
     }
@@ -467,7 +467,7 @@
         dest.writeInt(mMetered);
         dest.writeInt(mRoaming);
         dest.writeInt(mDefaultNetwork);
-        dest.writeInt(mSubType);
+        dest.writeInt(mRatType);
         dest.writeInt(mOemManaged);
         dest.writeInt(mSubscriberIdMatchRule);
     }
@@ -500,8 +500,8 @@
             builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString(
                     mDefaultNetwork));
         }
-        if (mSubType != NETWORK_TYPE_ALL) {
-            builder.append(", subType=").append(mSubType);
+        if (mRatType != NETWORK_TYPE_ALL) {
+            builder.append(", ratType=").append(mRatType);
         }
         if (mOemManaged != OEM_MANAGED_ALL) {
             builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
@@ -514,7 +514,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
-                mMetered, mRoaming, mDefaultNetwork, mSubType, mOemManaged, mSubscriberIdMatchRule);
+                mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule);
     }
 
     @Override
@@ -526,7 +526,7 @@
                     && mMetered == other.mMetered
                     && mRoaming == other.mRoaming
                     && mDefaultNetwork == other.mDefaultNetwork
-                    && mSubType == other.mSubType
+                    && mRatType == other.mRatType
                     && mOemManaged == other.mOemManaged
                     && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
                     && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
@@ -593,7 +593,7 @@
 
     /**
      * Get the set of Wifi Network Keys of the template.
-     * See {@link WifiInfo#getCurrentNetworkKey()}.
+     * See {@link WifiInfo#getNetworkKey()}.
      */
     @NonNull
     public Set<String> getWifiNetworkKeys() {
@@ -635,7 +635,7 @@
      * Get the Radio Access Technology(RAT) type filter of the template.
      */
     public int getRatType() {
-        return mSubType;
+        return mRatType;
     }
 
     /**
@@ -708,8 +708,8 @@
     }
 
     private boolean matchesCollapsedRatType(NetworkIdentity ident) {
-        return mSubType == NETWORK_TYPE_ALL
-                || getCollapsedRatType(mSubType) == getCollapsedRatType(ident.mSubType);
+        return mRatType == NETWORK_TYPE_ALL
+                || getCollapsedRatType(mRatType) == getCollapsedRatType(ident.mRatType);
     }
 
     /**
@@ -729,7 +729,7 @@
      * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is
      * empty.
      *
-     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()}
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
      *                  to know details about the key.
      */
     private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
@@ -837,7 +837,7 @@
         switch (ident.mType) {
             case TYPE_WIFI:
                 return matchesSubscriberId(ident.mSubscriberId)
-                        && matchesWifiNetworkKey(ident.mNetworkId);
+                        && matchesWifiNetworkKey(ident.mWifiNetworkKey);
             default:
                 return false;
         }
@@ -1059,9 +1059,9 @@
          * the intention of matching any Wifi Network Key.
          *
          * @param wifiNetworkKeys the list of Wifi Network Key,
-         *                        see {@link WifiInfo#getCurrentNetworkKey()}.
+         *                        see {@link WifiInfo#getNetworkKey()}.
          *                        Or an empty list to match all networks.
-         *                        Note that {@code getCurrentNetworkKey()} might get null key
+         *                        Note that {@code getNetworkKey()} might get null key
          *                        when wifi disconnects. However, the caller should never invoke
          *                        this function with a null Wifi Network Key since such statistics
          *                        never exists.
diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
index 1af32bf..c2f0cdf 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
@@ -16,8 +16,9 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -27,9 +28,10 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.MediaPlayer;
+import android.os.Binder;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.server.NetworkManagementSocketTagger;
 
@@ -37,8 +39,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.net.DatagramSocket;
 import java.net.Socket;
 import java.net.SocketException;
@@ -177,25 +177,12 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private synchronized static INetworkStatsService getStatsService() {
         if (sStatsService == null) {
-            sStatsService = getStatsBinder();
+            throw new IllegalStateException("TrafficStats not initialized, uid="
+                    + Binder.getCallingUid());
         }
         return sStatsService;
     }
 
-    @Nullable
-    private static INetworkStatsService getStatsBinder() {
-        try {
-            final Method getServiceMethod = Class.forName("android.os.ServiceManager")
-                    .getDeclaredMethod("getService", new Class[]{String.class});
-            final IBinder binder = (IBinder) getServiceMethod.invoke(
-                    null, Context.NETWORK_STATS_SERVICE);
-            return INetworkStatsService.Stub.asInterface(binder);
-        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException
-                | InvocationTargetException e) {
-            throw new NullPointerException("Cannot get INetworkStatsService: " + e);
-        }
-    }
-
     /**
      * Snapshot of {@link NetworkStats} when the currently active profiling
      * session started, or {@code null} if no session active.
@@ -210,6 +197,45 @@
     private static final String LOOPBACK_IFACE = "lo";
 
     /**
+     * Initialization {@link TrafficStats} with the context, to
+     * allow {@link TrafficStats} to fetch the needed binder.
+     *
+     * @param context a long-lived context, such as the application context or system
+     *                server context.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SuppressLint("VisiblySynchronized")
+    public static synchronized void init(@NonNull final Context context) {
+        if (sStatsService != null) {
+            throw new IllegalStateException("TrafficStats is already initialized, uid="
+                    + Binder.getCallingUid());
+        }
+        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/UnderlyingNetworkInfo.java b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
index 33f9375..7ab53b1 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
@@ -60,7 +60,7 @@
         mOwnerUid = in.readInt();
         mIface = in.readString();
         List<String> underlyingIfaces = new ArrayList<>();
-        in.readList(underlyingIfaces, null /*classLoader*/);
+        in.readList(underlyingIfaces, null /*classLoader*/, java.lang.String.class);
         mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
     }
 
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 b261e16..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: [
@@ -66,6 +68,7 @@
 filegroup {
     name: "services.connectivity-ethernet-sources",
     srcs: [
+        "src/com/android/server/net/DelayedDiskWrite.java",
         "src/com/android/server/net/IpConfigStore.java",
     ],
     path: "src",
@@ -97,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/services/core/java/com/android/server/net/DelayedDiskWrite.java b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java
similarity index 82%
rename from services/core/java/com/android/server/net/DelayedDiskWrite.java
rename to packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java
index 8f09eb7..35dc455 100644
--- a/services/core/java/com/android/server/net/DelayedDiskWrite.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java
@@ -26,21 +26,37 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 
+/**
+ * This class provides APIs to do a delayed data write to a given {@link OutputStream}.
+ */
 public class DelayedDiskWrite {
+    private static final String TAG = "DelayedDiskWrite";
+
     private HandlerThread mDiskWriteHandlerThread;
     private Handler mDiskWriteHandler;
     /* Tracks multiple writes on the same thread */
     private int mWriteSequence = 0;
-    private final String TAG = "DelayedDiskWrite";
 
+    /**
+     * Used to do a delayed data write to a given {@link OutputStream}.
+     */
     public interface Writer {
-        public void onWriteCalled(DataOutputStream out) throws IOException;
+        /**
+         * write data to a given {@link OutputStream}.
+         */
+        void onWriteCalled(DataOutputStream out) throws IOException;
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w) {
         write(filePath, w, true);
     }
 
+    /**
+     * Do a delayed data write to a given output stream opened from filePath.
+     */
     public void write(final String filePath, final Writer w, final boolean open) {
         if (TextUtils.isEmpty(filePath)) {
             throw new IllegalArgumentException("empty file path");
@@ -77,7 +93,7 @@
             if (out != null) {
                 try {
                     out.close();
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
 
             // Quit if no more writes sent
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 0abc523..243d621 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -19,12 +19,16 @@
 import static android.Manifest.permission.NETWORK_STATS_PROVIDER;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.app.usage.NetworkStatsManager.PREFIX_DEV;
+import static android.app.usage.NetworkStatsManager.PREFIX_UID;
+import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG;
+import static android.app.usage.NetworkStatsManager.PREFIX_XT;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.net.NetworkIdentity.SUBTYPE_COMBINED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.IFACE_VT;
@@ -47,23 +51,6 @@
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.net.TrafficStats.UNSUPPORTED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
-import static android.provider.Settings.Global.NETSTATS_AUGMENT_ENABLED;
-import static android.provider.Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED;
-import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION;
-import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE;
-import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES;
-import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE;
-import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
-import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
-import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
-import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
-import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
-import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
-import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE;
-import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION;
-import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE;
-import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES;
-import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
@@ -112,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;
@@ -119,12 +107,10 @@
 import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 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;
@@ -154,6 +140,7 @@
 import com.android.net.module.util.BestClock;
 import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.LocationPermissionChecker;
 import com.android.net.module.util.NetworkStatsUtils;
 import com.android.net.module.util.PermissionUtils;
 
@@ -216,6 +203,10 @@
     private static final int LOG_TAG_NETSTATS_MOBILE_SAMPLE = 51100;
     private static final int LOG_TAG_NETSTATS_WIFI_SAMPLE = 51101;
 
+    // TODO: Replace the hardcoded string and move it into ConnectivitySettingsManager.
+    private static final String NETSTATS_COMBINE_SUBTYPE_ENABLED =
+            "netstats_combine_subtype_enabled";
+
     private final Context mContext;
     private final NetworkStatsFactory mStatsFactory;
     private final AlarmManager mAlarmManager;
@@ -242,11 +233,6 @@
 
     private PendingIntent mPollIntent;
 
-    private static final String PREFIX_DEV = "dev";
-    private static final String PREFIX_XT = "xt";
-    private static final String PREFIX_UID = "uid";
-    private static final String PREFIX_UID_TAG = "uid_tag";
-
     /**
      * Settings that can be changed externally.
      */
@@ -256,9 +242,9 @@
         boolean getSampleEnabled();
         boolean getAugmentEnabled();
         /**
-         * When enabled, all mobile data is reported under {@link NetworkIdentity#SUBTYPE_COMBINED}.
-         * When disabled, mobile data is broken down by a granular subtype representative of the
-         * actual subtype. {@see NetworkTemplate#getCollapsedRatType}.
+         * When enabled, all mobile data is reported under {@link NetworkTemplate#NETWORK_TYPE_ALL}.
+         * When disabled, mobile data is broken down by a granular ratType representative of the
+         * actual ratType. {@see NetworkTemplate#getCollapsedRatType}.
          * Enabling this decreases the level of detail but saves performance, disk space and
          * amount of data logged.
          */
@@ -305,6 +291,9 @@
     /** Set of any ifaces associated with mobile networks since boot. */
     private volatile String[] mMobileIfaces = new String[0];
 
+    /** Set of any ifaces associated with wifi networks since boot. */
+    private volatile String[] mWifiIfaces = new String[0];
+
     /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
     @GuardedBy("mStatsLock")
     private Network[] mDefaultNetworks = new Network[0];
@@ -365,6 +354,12 @@
     @NonNull
     private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
 
+    @NonNull
+    private final LocationPermissionChecker mLocationPermissionChecker;
+
+    @NonNull
+    private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+
     private static @NonNull File getDefaultSystemDir() {
         return new File(Environment.getDataDirectory(), "system");
     }
@@ -427,7 +422,7 @@
         final NetworkStatsService service = new NetworkStatsService(context,
                 INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
                 alarmManager, wakeLock, getDefaultClock(),
-                new DefaultNetworkStatsSettings(context), new NetworkStatsFactory(netd),
+                new DefaultNetworkStatsSettings(), new NetworkStatsFactory(context),
                 new NetworkStatsObservers(), getDefaultSystemDir(), getDefaultBaseDir(),
                 new Dependencies());
 
@@ -457,10 +452,13 @@
         handlerThread.start();
         mHandler = new NetworkStatsHandler(handlerThread.getLooper());
         mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext,
-                new HandlerExecutor(mHandler), this);
+                (command) -> mHandler.post(command) , this);
         mContentResolver = mContext.getContentResolver();
         mContentObserver = mDeps.makeContentObserver(mHandler, mSettings,
                 mNetworkStatsSubscriptionsMonitor);
+        mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+        mInterfaceMapUpdater = mDeps.makeBpfInterfaceMapUpdater(mContext, mHandler);
+        mInterfaceMapUpdater.start();
     }
 
     /**
@@ -508,6 +506,20 @@
                 }
             };
         }
+
+        /**
+         * @see LocationPermissionChecker
+         */
+        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);
+        }
     }
 
     /**
@@ -556,7 +568,7 @@
         // watch for tethering changes
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
         tetheringManager.registerTetheringEventCallback(
-                new HandlerExecutor(mHandler), mTetherListener);
+                (command) -> mHandler.post(command), mTetherListener);
 
         // listen for periodic polling events
         final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
@@ -590,13 +602,13 @@
                 mSettings.getPollInterval(), pollIntent);
 
         mContentResolver.registerContentObserver(Settings.Global
-                .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED),
+                .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED),
                         false /* notifyForDescendants */, mContentObserver);
 
         // Post a runnable on handler thread to call onChange(). It's for getting current value of
         // NETSTATS_COMBINE_SUBTYPE_ENABLED to decide start or stop monitoring RAT type changes.
         mHandler.post(() -> mContentObserver.onChange(false, Settings.Global
-                .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED)));
+                .getUriFor(NETSTATS_COMBINE_SUBTYPE_ENABLED)));
 
         registerGlobalAlert();
     }
@@ -773,6 +785,7 @@
             @Override
             public NetworkStats getDeviceSummaryForNetwork(
                     NetworkTemplate template, long start, long end) {
+                enforceTemplatePermissions(template, callingPackage);
                 return internalGetSummaryForNetwork(template, restrictedFlags, start, end,
                         mAccessLevel, mCallingUid);
             }
@@ -780,19 +793,33 @@
             @Override
             public NetworkStats getSummaryForNetwork(
                     NetworkTemplate template, long start, long end) {
+                enforceTemplatePermissions(template, callingPackage);
                 return internalGetSummaryForNetwork(template, restrictedFlags, start, end,
                         mAccessLevel, mCallingUid);
             }
 
+            // TODO: Remove this after all callers are removed.
             @Override
             public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
+                enforceTemplatePermissions(template, callingPackage);
                 return internalGetHistoryForNetwork(template, restrictedFlags, fields,
-                        mAccessLevel, mCallingUid);
+                        mAccessLevel, mCallingUid, Long.MIN_VALUE, Long.MAX_VALUE);
+            }
+
+            @Override
+            public NetworkStatsHistory getHistoryIntervalForNetwork(NetworkTemplate template,
+                    int fields, long start, long end) {
+                enforceTemplatePermissions(template, callingPackage);
+                // TODO(b/200768422): Redact returned history if the template is location
+                //  sensitive but the caller is not privileged.
+                return internalGetHistoryForNetwork(template, restrictedFlags, fields,
+                        mAccessLevel, mCallingUid, start, end);
             }
 
             @Override
             public NetworkStats getSummaryForAllUid(
                     NetworkTemplate template, long start, long end, boolean includeTags) {
+                enforceTemplatePermissions(template, callingPackage);
                 try {
                     final NetworkStats stats = getUidComplete()
                             .getSummary(template, start, end, mAccessLevel, mCallingUid);
@@ -810,6 +837,7 @@
             @Override
             public NetworkStats getTaggedSummaryForAllUid(
                     NetworkTemplate template, long start, long end) {
+                enforceTemplatePermissions(template, callingPackage);
                 try {
                     final NetworkStats tagStats = getUidTagComplete()
                             .getSummary(template, start, end, mAccessLevel, mCallingUid);
@@ -822,6 +850,7 @@
             @Override
             public NetworkStatsHistory getHistoryForUid(
                     NetworkTemplate template, int uid, int set, int tag, int fields) {
+                enforceTemplatePermissions(template, callingPackage);
                 // NOTE: We don't augment UID-level statistics
                 if (tag == TAG_NONE) {
                     return getUidComplete().getHistory(template, null, uid, set, tag, fields,
@@ -836,6 +865,9 @@
             public NetworkStatsHistory getHistoryIntervalForUid(
                     NetworkTemplate template, int uid, int set, int tag, int fields,
                     long start, long end) {
+                enforceTemplatePermissions(template, callingPackage);
+                // TODO(b/200768422): Redact returned history if the template is location
+                //  sensitive but the caller is not privileged.
                 // NOTE: We don't augment UID-level statistics
                 if (tag == TAG_NONE) {
                     return getUidComplete().getHistory(template, null, uid, set, tag, fields,
@@ -857,6 +889,26 @@
         };
     }
 
+    private void enforceTemplatePermissions(@NonNull NetworkTemplate template,
+            @NonNull String callingPackage) {
+        // For a template with wifi network keys, it is possible for a malicious
+        // client to track the user locations via querying data usage. Thus, enforce
+        // fine location permission check.
+        if (!template.getWifiNetworkKeys().isEmpty()) {
+            final boolean canAccessFineLocation = mLocationPermissionChecker
+                    .checkCallersLocationPermission(callingPackage,
+                    null /* featureId */,
+                            Binder.getCallingUid(),
+                            false /* coarseForTargetSdkLessThanQ */,
+                            null /* message */);
+            if (!canAccessFineLocation) {
+                throw new SecurityException("Access fine location is required when querying"
+                        + " with wifi network keys, make sure the app has the necessary"
+                        + "permissions and the location toggle is on.");
+            }
+        }
+    }
+
     private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
         return NetworkStatsAccess.checkAccessLevel(
                 mContext, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage);
@@ -893,7 +945,7 @@
         // We've been using pure XT stats long enough that we no longer need to
         // splice DEV and XT together.
         final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
-                accessLevel, callingUid);
+                accessLevel, callingUid, start, end);
 
         final long now = System.currentTimeMillis();
         final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
@@ -910,14 +962,14 @@
      * appropriate.
      */
     private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template,
-            int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid) {
+            int flags, int fields, @NetworkStatsAccess.Level int accessLevel, int callingUid,
+            long start, long end) {
         // We've been using pure XT stats long enough that we no longer need to
         // splice DEV and XT together.
         final SubscriptionPlan augmentPlan = resolveSubscriptionPlan(template, flags);
         synchronized (mStatsLock) {
             return mXtStatsCached.getHistory(template, augmentPlan,
-                    UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, Long.MAX_VALUE,
-                    accessLevel, callingUid);
+                    UID_ALL, SET_ALL, TAG_NONE, fields, start, end, accessLevel, callingUid);
         }
     }
 
@@ -948,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);
@@ -968,11 +1029,15 @@
     }
 
     @Override
-    public NetworkStats getDetailedUidStats(String[] requiredIfaces) {
+    public NetworkStats getUidStatsForTransport(int transport) {
         enforceAnyPermissionOf(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
         try {
+            final String[] relevantIfaces =
+                    transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+            // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
+            // interfaces, so this is not useful, remove it.
             final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(requiredIfaces);
+                    mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
             return getNetworkStatsUidDetail(ifacesToQuery);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
@@ -1092,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);
         }
@@ -1331,16 +1395,18 @@
 
         final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
         final ArraySet<String> mobileIfaces = new ArraySet<>();
+        final ArraySet<String> wifiIfaces = new ArraySet<>();
         for (NetworkStateSnapshot snapshot : snapshots) {
             final int displayTransport =
                     getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
             final boolean isMobile = (NetworkCapabilities.TRANSPORT_CELLULAR == displayTransport);
+            final boolean isWifi = (NetworkCapabilities.TRANSPORT_WIFI == displayTransport);
             final boolean isDefault = CollectionUtils.contains(
                     mDefaultNetworks, snapshot.getNetwork());
-            final int subType = combineSubtypeEnabled ? SUBTYPE_COMBINED
-                    : getSubTypeForStateSnapshot(snapshot);
+            final int ratType = combineSubtypeEnabled ? NetworkTemplate.NETWORK_TYPE_ALL
+                    : getRatTypeForStateSnapshot(snapshot);
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
-                    isDefault, subType);
+                    isDefault, ratType);
 
             // Traffic occurring on the base interface is always counted for
             // both total usage and UID details.
@@ -1355,12 +1421,12 @@
                 // VT is considered always metered in framework's layer. If VT is not metered
                 // per carrier's policy, modem will report 0 usage for VT calls.
                 if (snapshot.getNetworkCapabilities().hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.getMetered()) {
+                        NetworkCapabilities.NET_CAPABILITY_IMS) && !ident.isMetered()) {
 
                     // Copy the identify from IMS one but mark it as metered.
                     NetworkIdentity vtIdent = new NetworkIdentity(ident.getType(),
-                            ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
-                            ident.getRoaming(), true /* metered */,
+                            ident.getRatType(), ident.getSubscriberId(), ident.getWifiNetworkKey(),
+                            ident.isRoaming(), true /* metered */,
                             true /* onDefaultNetwork */, ident.getOemManaged());
                     final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot);
                     findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent);
@@ -1370,6 +1436,9 @@
                 if (isMobile) {
                     mobileIfaces.add(baseIface);
                 }
+                if (isWifi) {
+                    wifiIfaces.add(baseIface);
+                }
             }
 
             // Traffic occurring on stacked interfaces is usually clatd.
@@ -1411,6 +1480,9 @@
                     if (isMobile) {
                         mobileIfaces.add(iface);
                     }
+                    if (isWifi) {
+                        wifiIfaces.add(iface);
+                    }
 
                     mStatsFactory.noteStackedIface(iface, baseIface);
                 }
@@ -1418,11 +1490,16 @@
         }
 
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
+        mWifiIfaces = wifiIfaces.toArray(new String[0]);
         // TODO (b/192758557): Remove debug log.
         if (CollectionUtils.contains(mMobileIfaces, null)) {
             throw new NullPointerException(
                     "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
         }
+        if (CollectionUtils.contains(mWifiIfaces, null)) {
+            throw new NullPointerException(
+                    "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
+        }
     }
 
     private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -1440,11 +1517,11 @@
     }
 
     /**
-     * For networks with {@code TRANSPORT_CELLULAR}, get subType that was obtained through
+     * For networks with {@code TRANSPORT_CELLULAR}, get ratType that was obtained through
      * {@link PhoneStateListener}. Otherwise, return 0 given that other networks with different
      * transport types do not actually fill this value.
      */
-    private int getSubTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
+    private int getRatTypeForStateSnapshot(@NonNull NetworkStateSnapshot state) {
         if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
             return 0;
         }
@@ -2191,24 +2268,11 @@
      * {@link android.provider.Settings.Global}.
      */
     private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
-        private final ContentResolver mResolver;
-
-        public DefaultNetworkStatsSettings(Context context) {
-            mResolver = Objects.requireNonNull(context.getContentResolver());
-            // TODO: adjust these timings for production builds
-        }
-
-        private long getGlobalLong(String name, long def) {
-            return Settings.Global.getLong(mResolver, name, def);
-        }
-        private boolean getGlobalBoolean(String name, boolean def) {
-            final int defInt = def ? 1 : 0;
-            return Settings.Global.getInt(mResolver, name, defInt) != 0;
-        }
+        DefaultNetworkStatsSettings() {}
 
         @Override
         public long getPollInterval() {
-            return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
+            return 30 * MINUTE_IN_MILLIS;
         }
         @Override
         public long getPollDelay() {
@@ -2216,25 +2280,23 @@
         }
         @Override
         public long getGlobalAlertBytes(long def) {
-            return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
+            return def;
         }
         @Override
         public boolean getSampleEnabled() {
-            return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true);
+            return true;
         }
         @Override
         public boolean getAugmentEnabled() {
-            return getGlobalBoolean(NETSTATS_AUGMENT_ENABLED, true);
+            return true;
         }
         @Override
         public boolean getCombineSubtypeEnabled() {
-            return getGlobalBoolean(NETSTATS_COMBINE_SUBTYPE_ENABLED, false);
+            return false;
         }
         @Override
         public Config getDevConfig() {
-            return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
-                    getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
-                    getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
+            return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
         }
         @Override
         public Config getXtConfig() {
@@ -2242,31 +2304,27 @@
         }
         @Override
         public Config getUidConfig() {
-            return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
-                    getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
+            return new Config(2 * HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
         }
         @Override
         public Config getUidTagConfig() {
-            return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
-                    getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
-                    getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
+            return new Config(2 * HOUR_IN_MILLIS, 5 * DAY_IN_MILLIS, 15 * DAY_IN_MILLIS);
         }
         @Override
         public long getDevPersistBytes(long def) {
-            return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def);
+            return def;
         }
         @Override
         public long getXtPersistBytes(long def) {
-            return getDevPersistBytes(def);
+            return def;
         }
         @Override
         public long getUidPersistBytes(long def) {
-            return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def);
+            return def;
         }
         @Override
         public long getUidTagPersistBytes(long def) {
-            return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
+            return def;
         }
     }
 
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index a3eb0ecc..ce58ff6 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -236,7 +236,8 @@
                 root.flags |= Root.FLAG_REMOVABLE_USB;
             }
 
-            if (volume.getType() != VolumeInfo.TYPE_EMULATED) {
+            if (volume.getType() != VolumeInfo.TYPE_EMULATED
+                    && volume.getType() != VolumeInfo.TYPE_STUB) {
                 root.flags |= Root.FLAG_SUPPORTS_EJECT;
             }
 
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 45253bb..b150e01 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -28,4 +28,9 @@
     <!-- Control whether status bar should distinguish HSPA data icon form UMTS
     data icon on devices -->
     <bool name="config_hspa_data_distinguishable">false</bool>
+
+    <integer-array name="config_supportedDreamComplications">
+    </integer-array>
+    <integer-array name="config_dreamComplicationsEnabledByDefault">
+    </integer-array>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f9ac01d..e4eab4b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1306,8 +1306,8 @@
 
     <!--  Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] -->
     <string name="zen_mode_enable_dialog_turn_on">Turn on</string>
-    <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
-    <string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string>
+    <!-- Priority mode: Title for the Priority mode dialog to turn on Priority mode. [CHAR LIMIT=50]-->
+    <string name="zen_mode_settings_turn_on_dialog_title" translatable="false">Turn on Priority mode</string>
     <!-- Sound: Summary for the Do not Disturb option when there is no automatic rules turned on. [CHAR LIMIT=NONE]-->
     <string name="zen_mode_settings_summary_off">Never</string>
     <!--[CHAR LIMIT=40] Zen Interruption level: Priority.  -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 5f2bef7..64a0781 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -31,9 +31,8 @@
     private int mLastDensity;
 
     public InterestingConfigChanges() {
-        this(ActivityInfo.CONFIG_LOCALE
-                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT
-                | ActivityInfo.CONFIG_ASSETS_PATHS);
+        this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION
+                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS);
     }
 
     public InterestingConfigChanges(int flags) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 2c862e685..389892e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -170,18 +170,6 @@
     }
 
     @VisibleForTesting
-    void registerIntentReceiver() {
-        mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter,
-                null, mReceiverHandler);
-    }
-
-    @VisibleForTesting
-    void registerProfileIntentReceiverForTest() {
-        mContext.registerReceiverAsUser(mProfileBroadcastReceiver, mUserHandle,
-                mProfileIntentFilter, null, mReceiverHandler);
-    }
-
-    @VisibleForTesting
     void addProfileHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
         mProfileIntentFilter.addAction(action);
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index aed2ec1..7168f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -38,6 +38,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.settingslib.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -45,9 +47,12 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 public class DreamBackend {
     private static final String TAG = "DreamBackend";
@@ -78,19 +83,41 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
-    public @interface WhenToDream{}
+    public @interface WhenToDream {}
 
     public static final int WHILE_CHARGING = 0;
     public static final int WHILE_DOCKED = 1;
     public static final int EITHER = 2;
     public static final int NEVER = 3;
 
+    /**
+     * The type of dream complications which can be provided by a
+     * {@link com.android.systemui.dreams.ComplicationProvider}.
+     */
+    @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = {
+            COMPLICATION_TYPE_TIME,
+            COMPLICATION_TYPE_DATE,
+            COMPLICATION_TYPE_WEATHER,
+            COMPLICATION_TYPE_AIR_QUALITY,
+            COMPLICATION_TYPE_CAST_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ComplicationType {}
+
+    public static final int COMPLICATION_TYPE_TIME = 1;
+    public static final int COMPLICATION_TYPE_DATE = 2;
+    public static final int COMPLICATION_TYPE_WEATHER = 3;
+    public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
+    public static final int COMPLICATION_TYPE_CAST_INFO = 5;
+
     private final Context mContext;
     private final IDreamManager mDreamManager;
     private final DreamInfoComparator mComparator;
     private final boolean mDreamsEnabledByDefault;
     private final boolean mDreamsActivatedOnSleepByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
+    private final Set<Integer> mSupportedComplications;
+    private final Set<Integer> mDefaultEnabledComplications;
 
     private static DreamBackend sInstance;
 
@@ -103,17 +130,31 @@
 
     public DreamBackend(Context context) {
         mContext = context.getApplicationContext();
+        final Resources resources = mContext.getResources();
+
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.getService(DreamService.DREAM_SERVICE));
         mComparator = new DreamInfoComparator(getDefaultDream());
-        mDreamsEnabledByDefault = mContext.getResources()
-                .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault);
-        mDreamsActivatedOnSleepByDefault = mContext.getResources()
-                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
-        mDreamsActivatedOnDockByDefault = mContext.getResources()
-                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
-        mDreamPreviewDefault = mContext.getResources().getDrawable(
+        mDreamsEnabledByDefault = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsEnabledByDefault);
+        mDreamsActivatedOnSleepByDefault = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+        mDreamsActivatedOnDockByDefault = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+        mDreamPreviewDefault = resources.getDrawable(
                 com.android.internal.R.drawable.default_dream_preview);
+
+        mSupportedComplications =
+                Arrays.stream(resources.getIntArray(R.array.config_supportedDreamComplications))
+                        .boxed()
+                        .collect(Collectors.toSet());
+
+        mDefaultEnabledComplications = Arrays.stream(
+                        resources.getIntArray(R.array.config_dreamComplicationsEnabledByDefault))
+                .boxed()
+                // A complication can only be enabled by default if it is also supported.
+                .filter(mSupportedComplications::contains)
+                .collect(Collectors.toSet());
     }
 
     public List<DreamInfo> getDreamInfos() {
@@ -242,7 +283,57 @@
             default:
                 break;
         }
+    }
 
+    /** Gets all complications which have been enabled by the user. */
+    public Set<Integer> getEnabledComplications() {
+        final String enabledComplications = Settings.Secure.getString(
+                mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS);
+
+        if (enabledComplications == null) {
+            return mDefaultEnabledComplications;
+        }
+
+        return parseFromString(enabledComplications);
+    }
+
+    /** Gets all dream complications which are supported on this device. **/
+    public Set<Integer> getSupportedComplications() {
+        return mSupportedComplications;
+    }
+
+    /**
+     * Enables or disables a particular dream complication.
+     *
+     * @param complicationType The dream complication to be enabled/disabled.
+     * @param value            If true, the complication is enabled. Otherwise it is disabled.
+     */
+    public void setComplicationEnabled(@ComplicationType int complicationType, boolean value) {
+        if (!mSupportedComplications.contains(complicationType)) return;
+
+        Set<Integer> enabledComplications = getEnabledComplications();
+        if (value) {
+            enabledComplications.add(complicationType);
+        } else {
+            enabledComplications.remove(complicationType);
+        }
+
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_ENABLED_COMPLICATIONS,
+                convertToString(enabledComplications));
+    }
+
+    private static String convertToString(Set<Integer> set) {
+        return set.stream()
+                .map(String::valueOf)
+                .collect(Collectors.joining(","));
+    }
+
+    private static Set<Integer> parseFromString(String string) {
+        return Arrays.stream(string.split(","))
+                .map(Integer::parseInt)
+                .collect(Collectors.toSet());
     }
 
     public boolean isEnabled() {
@@ -311,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/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index 3e95b01..5e9ac5a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -16,23 +16,16 @@
 
 package com.android.settingslib.net;
 
-import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
-import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-
+import android.annotation.NonNull;
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
-import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.net.TrafficStats;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.text.format.DateUtils;
 import android.util.Pair;
+import android.util.Range;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.loader.content.AsyncTaskLoader;
@@ -52,8 +45,6 @@
     protected final NetworkTemplate mNetworkTemplate;
     private final NetworkPolicy mPolicy;
     private final ArrayList<Long> mCycles;
-    @VisibleForTesting
-    final INetworkStatsService mNetworkStatsService;
 
     protected NetworkCycleDataLoader(Builder<?> builder) {
         super(builder.mContext);
@@ -61,8 +52,6 @@
         mCycles = builder.mCycles;
         mNetworkStatsManager = (NetworkStatsManager)
             builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
-        mNetworkStatsService = INetworkStatsService.Stub.asInterface(
-            ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
         final NetworkPolicyEditor policyEditor =
             new NetworkPolicyEditor(NetworkPolicyManager.from(builder.mContext));
         policyEditor.read();
@@ -112,23 +101,20 @@
 
     @VisibleForTesting
     void loadFourWeeksData() {
+        if (mNetworkTemplate == null) return;
+        final NetworkStats stats = mNetworkStatsManager.queryDetailsForDevice(
+                mNetworkTemplate, Long.MIN_VALUE, Long.MAX_VALUE);
         try {
-            final INetworkStatsSession networkSession = mNetworkStatsService.openSession();
-            final NetworkStatsHistory networkHistory = networkSession.getHistoryForNetwork(
-                mNetworkTemplate, FIELD_RX_BYTES | FIELD_TX_BYTES);
-            final long historyStart = networkHistory.getStart();
-            final long historyEnd = networkHistory.getEnd();
+            final Range<Long> historyTimeRange = getTimeRangeOf(stats);
 
-            long cycleEnd = historyEnd;
-            while (cycleEnd > historyStart) {
+            long cycleEnd = historyTimeRange.getUpper();
+            while (cycleEnd > historyTimeRange.getLower()) {
                 final long cycleStart = cycleEnd - (DateUtils.WEEK_IN_MILLIS * 4);
                 recordUsage(cycleStart, cycleEnd);
                 cycleEnd = cycleStart;
             }
-
-            TrafficStats.closeQuietly(networkSession);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+            // Empty history, ignore.
         }
     }
 
@@ -169,6 +155,32 @@
         return bytes;
     }
 
+    @NonNull
+    @VisibleForTesting
+    Range getTimeRangeOf(@NonNull NetworkStats stats) {
+        long start = Long.MAX_VALUE;
+        long end = Long.MIN_VALUE;
+        while (hasNextBucket(stats)) {
+            final NetworkStats.Bucket bucket = getNextBucket(stats);
+            start = Math.min(start, bucket.getStartTimeStamp());
+            end = Math.max(end, bucket.getEndTimeStamp());
+        }
+        return new Range(start, end);
+    }
+
+    @VisibleForTesting
+    boolean hasNextBucket(@NonNull NetworkStats stats) {
+        return stats.hasNextBucket();
+    }
+
+    @NonNull
+    @VisibleForTesting
+    NetworkStats.Bucket getNextBucket(@NonNull NetworkStats stats) {
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+        stats.getNextBucket(bucket);
+        return bucket;
+    }
+
     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     public ArrayList<Long> getCycles() {
         return mCycles;
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/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 63153f8..10ccd22 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -708,11 +708,11 @@
             throws RemoteException {
 
         if (ownerApps != null) {
-            when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(0)))
+            when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(0)))
                 .thenReturn(new ParceledListSlice<>(ownerApps));
         }
         if (profileApps != null) {
-            when(mApplicationsState.mIpm.getInstalledApplications(anyInt(), eq(PROFILE_USERID)))
+            when(mApplicationsState.mIpm.getInstalledApplications(anyLong(), eq(PROFILE_USERID)))
                 .thenReturn(new ParceledListSlice<>(profileApps));
         }
         final InterestingConfigChanges configChanges = mock(InterestingConfigChanges.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index bee466d..852ac5c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -129,7 +129,6 @@
     @Test
     public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
 
         mContext.sendBroadcast(mIntent);
@@ -143,7 +142,6 @@
     @Test
     public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
 
         mContext.sendBroadcast(mIntent);
@@ -169,7 +167,6 @@
     @Test
     public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -182,7 +179,6 @@
     @Test
     public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -196,7 +192,6 @@
     public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() {
         when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -210,7 +205,6 @@
     public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() {
         when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -224,7 +218,6 @@
     public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() {
         when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -361,7 +354,6 @@
 
     @Test
     public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -377,7 +369,6 @@
 
     @Test
     public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() {
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -394,7 +385,6 @@
 
     @Test
     public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() {
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -410,7 +400,6 @@
 
     @Test
     public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() {
-        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index afea083..4f8fa2f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -84,7 +84,6 @@
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
         mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
                 mDeviceManager, mEventManager);
-        mEventManager.registerProfileIntentReceiverForTest();
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
index 4444e63..c1cc3ae 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -33,6 +33,7 @@
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -54,6 +55,7 @@
     }
 
     @Test
+    @Ignore
     public void testBroadcastReceiver() {
         final AbstractConnectivityPreferenceController preferenceController =
                 spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
new file mode 100644
index 0000000..53d4653
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.settingslib.dream;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.settingslib.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowSettings;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowSettings.ShadowSecure.class})
+public final class DreamBackendTest {
+    private static final int[] SUPPORTED_DREAM_COMPLICATIONS = {1, 2, 3};
+    private static final int[] DEFAULT_DREAM_COMPLICATIONS = {1, 3, 4};
+
+    @Mock
+    private Context mContext;
+    private DreamBackend mBackend;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+
+        final Resources res = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(res);
+        when(res.getIntArray(R.array.config_supportedDreamComplications)).thenReturn(
+                SUPPORTED_DREAM_COMPLICATIONS);
+        when(res.getIntArray(R.array.config_dreamComplicationsEnabledByDefault)).thenReturn(
+                DEFAULT_DREAM_COMPLICATIONS);
+        mBackend = new DreamBackend(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSettings.ShadowSecure.reset();
+    }
+
+    @Test
+    public void testSupportedComplications() {
+        assertThat(mBackend.getSupportedComplications()).containsExactly(1, 2, 3);
+    }
+
+    @Test
+    public void testGetEnabledDreamComplications_default() {
+        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3);
+    }
+
+    @Test
+    public void testEnableComplication() {
+        mBackend.setComplicationEnabled(/* complicationType= */ 2, true);
+        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 2, 3);
+    }
+
+    @Test
+    public void testEnableComplication_notSupported() {
+        mBackend.setComplicationEnabled(/* complicationType= */ 5, true);
+        assertThat(mBackend.getEnabledComplications()).containsExactly(1, 3);
+    }
+
+    @Test
+    public void testDisableComplication() {
+        mBackend.setComplicationEnabled(/* complicationType= */ 1, false);
+        assertThat(mBackend.getEnabledComplications()).containsExactly(3);
+    }
+}
+
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
index 8ec577e..06b6fc8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java
@@ -22,7 +22,6 @@
 
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertSame;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -67,7 +66,7 @@
 
     @Test
     public void buttonClicked() {
-        ComponentName componentName = mock(ComponentName.class);
+        ComponentName componentName = new ComponentName("com.android.test", "AThing");
         RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin(
                 componentName, new UserHandle(UserHandle.myUserId()));
 
@@ -83,6 +82,6 @@
         assertEquals(Settings.SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS,
                 intentCaptor.getValue().getStringExtra(
                         Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY));
-        assertSame(componentName, intentCaptor.getValue().getComponent());
+        assertEquals(componentName.getPackageName(), intentCaptor.getValue().getPackage());
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 2d53831..aa0ef91 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -46,6 +46,7 @@
 import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -731,6 +732,7 @@
     }
 
     @Test
+    @Ignore
     public void shouldDisableMediaOutput_infosSizeEqual1_returnsTrue() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         final List<MediaRoute2Info> infos = new ArrayList<>();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index 74b9151..c79440e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -16,24 +16,24 @@
 
 package com.android.settingslib.net;
 
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.nullable;
+import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
+import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.INetworkStatsService;
-import android.net.INetworkStatsSession;
 import android.net.NetworkPolicy;
 import android.net.NetworkPolicyManager;
-import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.os.RemoteException;
 import android.text.format.DateUtils;
 import android.util.Range;
 
@@ -49,6 +49,8 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
 
 @RunWith(RobolectricTestRunner.class)
 public class NetworkCycleDataLoaderTest {
@@ -63,8 +65,6 @@
     private NetworkPolicy mPolicy;
     @Mock
     private Iterator<Range<ZonedDateTime>> mIterator;
-    @Mock
-    private INetworkStatsService mNetworkStatsService;
 
     private NetworkCycleDataTestLoader mLoader;
 
@@ -132,20 +132,24 @@
         verify(mLoader).recordUsage(nowInMs, nowInMs);
     }
 
+    private NetworkStats.Bucket makeMockBucket(int uid, long rxBytes, long txBytes,
+            long start, long end) {
+        NetworkStats.Bucket ret = mock(NetworkStats.Bucket.class);
+        when(ret.getUid()).thenReturn(uid);
+        when(ret.getRxBytes()).thenReturn(rxBytes);
+        when(ret.getTxBytes()).thenReturn(txBytes);
+        when(ret.getStartTimeStamp()).thenReturn(start);
+        when(ret.getEndTimeStamp()).thenReturn(end);
+        return ret;
+    }
+
     @Test
-    public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() throws RemoteException {
+    public void loadFourWeeksData_shouldRecordUsageForLast4Weeks() {
         mLoader = spy(new NetworkCycleDataTestLoader(mContext));
-        ReflectionHelpers.setField(mLoader, "mNetworkStatsService", mNetworkStatsService);
-        final INetworkStatsSession networkSession = mock(INetworkStatsSession.class);
-        when(mNetworkStatsService.openSession()).thenReturn(networkSession);
-        final NetworkStatsHistory networkHistory = mock(NetworkStatsHistory.class);
-        when(networkSession.getHistoryForNetwork(nullable(NetworkTemplate.class), anyInt()))
-            .thenReturn(networkHistory);
         final long now = System.currentTimeMillis();
         final long fourWeeksAgo = now - (DateUtils.WEEK_IN_MILLIS * 4);
         final long twoDaysAgo = now - (DateUtils.DAY_IN_MILLIS * 2);
-        when(networkHistory.getStart()).thenReturn(twoDaysAgo);
-        when(networkHistory.getEnd()).thenReturn(now);
+        mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, twoDaysAgo, now));
 
         mLoader.loadFourWeeksData();
 
@@ -173,10 +177,31 @@
         verify(mLoader).recordUsage(thirtyDaysAgo, twentyDaysAgo);
     }
 
+    @Test
+    public void getTimeRangeOf() {
+        mLoader = spy(new NetworkCycleDataTestLoader(mContext));
+        // If empty, new Range(MAX_VALUE, MIN_VALUE) will be constructed. Hence, the function
+        // should throw.
+        assertThrows(IllegalArgumentException.class,
+                () -> mLoader.getTimeRangeOf(mock(NetworkStats.class)));
+
+        mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10));
+        // Feed the function with unused NetworkStats. The actual data injection is
+        // done by addBucket.
+        assertEquals(new Range(0L, 10L), mLoader.getTimeRangeOf(mock(NetworkStats.class)));
+
+        mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 0, 10));
+        mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 30, 40));
+        mLoader.addBucket(makeMockBucket(UID_ALL, 123, 456, 10, 25));
+        assertEquals(new Range(0L, 40L), mLoader.getTimeRangeOf(mock(NetworkStats.class)));
+    }
+
     public class NetworkCycleDataTestLoader extends NetworkCycleDataLoader<List<NetworkCycleData>> {
+        private final Queue<NetworkStats.Bucket> mMockedBuckets = new LinkedBlockingQueue<>();
 
         private NetworkCycleDataTestLoader(Context context) {
-            super(NetworkCycleDataLoader.builder(mContext));
+            super(NetworkCycleDataLoader.builder(mContext)
+                    .setNetworkTemplate(mock(NetworkTemplate.class)));
             mContext = context;
         }
 
@@ -188,5 +213,19 @@
         List<NetworkCycleData> getCycleUsage() {
             return null;
         }
+
+        public void addBucket(NetworkStats.Bucket bucket) {
+            mMockedBuckets.add(bucket);
+        }
+
+        @Override
+        public boolean hasNextBucket(@NonNull NetworkStats unused) {
+            return !mMockedBuckets.isEmpty();
+        }
+
+        @Override
+        public NetworkStats.Bucket getNextBucket(@NonNull NetworkStats unused) {
+            return mMockedBuckets.remove();
+        }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 2bd20a9..30267f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -19,11 +19,15 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
+import android.net.wifi.WifiManager;
+
+import androidx.test.core.app.ApplicationProvider;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -36,7 +40,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class AccessPointPreferenceTest {
 
-    private Context mContext = RuntimeEnvironment.application;
+    private Context mContext;
 
     @Mock
     private AccessPoint mockAccessPoint;
@@ -53,6 +57,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class));
 
         when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 7c2b904..e7b3fe9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,6 +33,7 @@
 import android.net.WifiKey;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -74,6 +76,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class));
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 2312525..5f549fd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -296,6 +296,7 @@
         VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCATION_SHOW_SYSTEM_OPS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> {
             if (TextUtils.isEmpty(value)) {
                 return true;
@@ -324,6 +325,7 @@
             return true;
         });
         VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FAST_PAIR_SCAN_ENABLED, BOOLEAN_VALIDATOR);
 
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c5f027b..7381e05 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1378,9 +1378,6 @@
                 Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
                 GlobalSettingsProto.Sys.STORAGE_CACHE_PERCENTAGE);
         dumpSetting(s, p,
-                Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
-                GlobalSettingsProto.Sys.STORAGE_CACHE_MAX_BYTES);
-        dumpSetting(s, p,
                 Settings.Global.SYS_UIDCPUPOWER,
                 GlobalSettingsProto.Sys.UIDCPUPOWER);
         p.end(sysToken);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a3f3995..720fb6c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -462,7 +462,6 @@
                     Settings.Global.SYNC_MANAGER_CONSTANTS,
                     Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
                     Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
-                    Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
                     Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
                     Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
                     Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c1a812f..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. -->
@@ -250,6 +251,7 @@
     <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
     <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
     <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
+    <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
     <uses-permission android:name="android.permission.MANAGE_UI_TRANSLATION" />
     <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
@@ -548,6 +550,10 @@
     <!-- Permission required for CTS test - PeopleManagerTest -->
     <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
 
+    <!-- Permissions required for CTS test - TrustTestCases -->
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+
     <!-- Permission required for CTS test - CtsGameManagerTestCases -->
     <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
 
@@ -623,10 +629,6 @@
     <!-- Permission required for CTS test - Notification test suite -->
     <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
 
-    <!-- Permission required for CTS test - CommunalManagerTest -->
-    <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
-    <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
-
     <!-- Permission required for CTS test - CaptioningManagerTest -->
     <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 92ae92e..f35f5dd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -153,6 +153,9 @@
     <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
     <uses-permission android:name="android.permission.SET_WALLPAPER"/>
 
+    <!-- Needed for WallpaperManager.getWallpaperDimAmount in StatusBar.updateTheme -->
+    <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
+
     <!-- Wifi Display -->
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index e1da744..8323e32 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -11,8 +11,10 @@
 awickham@google.com
 beverlyt@google.com
 brockman@google.com
+brzezinski@google.com
 brycelee@google.com
 ccassidy@google.com
+chrisgollner@google.com
 cinek@google.com
 cwren@google.com
 dupin@google.com
@@ -43,6 +45,8 @@
 mrcasey@google.com
 mrenouf@google.com
 nesciosquid@google.com
+nickchameyev@google.com
+nicomazz@google.com
 ogunwale@google.com
 peanutbutter@google.com
 pinyaoting@google.com
@@ -67,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-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index dfc3e63..ecb3cb3 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -22,21 +22,6 @@
     android:layout_height="48dp"
     android:gravity="center_vertical">
 
-    <com.android.systemui.statusbar.AlphaOptimizedImageView
-        android:id="@android:id/edit"
-        android:layout_width="0dp"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
-        android:layout_weight="1"
-        android:background="@drawable/qs_footer_action_chip_background"
-        android:clickable="true"
-        android:clipToPadding="false"
-        android:contentDescription="@string/accessibility_quick_settings_edit"
-        android:focusable="true"
-        android:padding="@dimen/qs_footer_icon_padding"
-        android:src="@*android:drawable/ic_mode_edit"
-        android:tint="?android:attr/textColorPrimary" />
-
     <com.android.systemui.statusbar.phone.MultiUserSwitch
         android:id="@+id/multi_user_switch"
         android:layout_width="0dp"
diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml
index c5bf4ce..2b83787 100644
--- a/packages/SystemUI/res-keyguard/values/bools.xml
+++ b/packages/SystemUI/res-keyguard/values/bools.xml
@@ -17,5 +17,4 @@
 <resources>
     <bool name="kg_show_ime_at_screen_on">true</bool>
     <bool name="kg_use_all_caps">true</bool>
-    <bool name="flag_active_unlock">false</bool>
 </resources>
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/drawable/qs_customizer_background_transition.xml b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml
index ed8f61a..6fa9eac 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="@color/qs_detail_transition"/>
+        <solid android:color="@android:color/transparent"/>
         <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml
index e5c7352..c23649d 100644
--- a/packages/SystemUI/res/drawable/qs_detail_background.xml
+++ b/packages/SystemUI/res/drawable/qs_detail_background.xml
@@ -17,7 +17,7 @@
     <item>
         <inset>
             <shape>
-                <solid android:color="@color/qs_detail_transition"/>
+                <solid android:color="@android:color/transparent"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius" />
             </shape>
         </inset>
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/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index e70084b..5cd9e94 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -43,7 +43,6 @@
                 android:id="@+id/build"
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
-                android:paddingStart="@dimen/qs_tile_margin_horizontal"
                 android:paddingEnd="4dp"
                 android:layout_weight="1"
                 android:clickable="true"
@@ -61,10 +60,23 @@
                 android:layout_gravity="center_vertical"
                 android:visibility="gone" />
 
-            <View
+            <FrameLayout
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
-                android:layout_weight="1" />
+                android:layout_weight="1">
+                <com.android.systemui.statusbar.AlphaOptimizedImageView
+                    android:id="@android:id/edit"
+                    android:layout_width="@dimen/qs_footer_action_button_size"
+                    android:layout_height="@dimen/qs_footer_action_button_size"
+                    android:layout_gravity="center_vertical|end"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:clickable="true"
+                    android:contentDescription="@string/accessibility_quick_settings_edit"
+                    android:focusable="true"
+                    android:padding="@dimen/qs_footer_icon_padding"
+                    android:src="@*android:drawable/ic_mode_edit"
+                    android:tint="?android:attr/textColorPrimary" />
+            </FrameLayout>
 
         </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
index 7c0ee665..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/colors.xml b/packages/SystemUI/res/values/colors.xml
index fc28f09..461a598 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -23,7 +23,6 @@
     <color name="system_bar_background_transparent">#00000000</color>
     <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
     <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
-    <color name="qs_detail_transition">#66FFFFFF</color>
     <color name="status_bar_clock_color">#FFFFFFFF</color>
     <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
 
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 65f22b8..fc2756e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -716,22 +716,6 @@
     <!-- Flag to enable privacy dot views, it shall be true for normal case -->
     <bool name="config_enablePrivacyDot">true</bool>
 
-    <!-- The positions widgets can be in defined as View.Gravity constants -->
-    <integer-array name="config_dreamComplicationPositions">
-    </integer-array>
-
-    <!-- Widget components to show as dream complications -->
-    <string-array name="config_dreamAppWidgetComplications" translatable="false">
-    </string-array>
-
-    <!-- Width percentage of dream complications -->
-    <item name="config_dreamComplicationWidthPercent" translatable="false" format="float"
-          type="dimen">0.33</item>
-
-    <!-- Height percentage of dream complications -->
-    <item name="config_dreamComplicationHeightPercent" translatable="false" format="float"
-          type="dimen">0.25</item>
-
     <!-- Flag to enable dream overlay service and its registration -->
     <bool name="config_dreamOverlayServiceEnabled">false</bool>
 
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 08fb2c6..7384ccc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -429,8 +429,8 @@
     <string name="accessibility_quick_settings_dnd_none_on">total silence</string>
     <!-- Content description of the do not disturb tile in quick settings when on in alarms only (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_dnd_alarms_on">alarms only</string>
-     <!-- Content description of the do not disturb tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_quick_settings_dnd">Do Not Disturb.</string>
+     <!-- Content description of the priority mode tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_quick_settings_dnd" translatable="false">Priority mode.</string>
     <!-- Content description of the bluetooth tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_quick_settings_bluetooth">Bluetooth.</string>
     <!-- Content description of the bluetooth tile in quick settings when on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -508,8 +508,8 @@
     <string name="ethernet_label">Ethernet</string>
 
     <!-- QuickSettings: Onboarding text that introduces users to long press on an option in order to view the option's menu in Settings [CHAR LIMIT=NONE] -->
-    <!-- QuickSettings: Do not disturb [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_dnd_label">Do Not Disturb</string>
+    <!-- QuickSettings: Priority mode [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_dnd_label" translatable="false">Priority mode</string>
     <!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
@@ -670,6 +670,10 @@
     <string name="quick_settings_dark_mode_secondary_label_on_at">On at <xliff:g id="time" example="10 pm">%s</xliff:g></string>
     <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] -->
     <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
+    <!-- QuickSettings: Secondary text for when the Dark theme will be enabled at bedtime. [CHAR LIMIT=40] -->
+    <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime">On at bedtime</string>
+    <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until bedtime ends. [CHAR LIMIT=40] -->
+    <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends">Until bedtime ends</string>
 
     <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] -->
     <string name="quick_settings_nfc_label">NFC</string>
@@ -892,8 +896,8 @@
     <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
     <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
 
-    <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] -->
-    <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string>
+    <!-- The text to show in the notifications shade when Priority mode is suppressing notifications. [CHAR LIMIT=100] -->
+    <string name="dnd_suppressing_shade_text" translatable="false">Notifications paused by Priority mode</string>
 
     <!-- Media projection permission dialog action text. [CHAR LIMIT=60] -->
     <string name="media_projection_action_text">Start now</string>
@@ -1315,8 +1319,8 @@
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level summary -->
     <string name="notification_channel_summary_priority_baseline">Shows at the top of conversation notifications and as a profile picture on lock screen</string>
     <string name="notification_channel_summary_priority_bubble">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble</string>
-    <string name="notification_channel_summary_priority_dnd">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb</string>
-    <string name="notification_channel_summary_priority_all">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb</string>
+    <string name="notification_channel_summary_priority_dnd" translatable="false">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Priority mode</string>
+    <string name="notification_channel_summary_priority_all" translatable="false">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Priority mode</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level -->
     <string name="notification_priority_title">Priority</string>
@@ -1507,8 +1511,8 @@
     <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. -->
     <string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
 
-    <!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] -->
-    <string name="volume_and_do_not_disturb">Do Not Disturb</string>
+    <!-- SysUI Tuner: Label for screen about priority mode settings [CHAR LIMIT=60] -->
+    <string name="volume_and_do_not_disturb" translatable="false">Priority mode</string>
 
     <!-- SysUI Tuner: Switch to control whether volume buttons enter/exit do
          not disturb [CHAR LIMIT=60] -->
@@ -1869,17 +1873,17 @@
     <!-- Label for when bluetooth is off in QS detail panel [CHAR LIMIT=NONE] -->
     <string name="bt_is_off">Bluetooth is off</string>
 
-    <!-- Label for when Do not disturb is off in QS detail panel [CHAR LIMIT=NONE] -->
-    <string name="dnd_is_off">Do Not Disturb is off</string>
+    <!-- Label for when Priority mode is off in QS detail panel [CHAR LIMIT=NONE] -->
+    <string name="dnd_is_off" translatable="false">Priority mode is off</string>
 
-    <!-- Prompt for when Do not disturb is on from automatic rule in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_auto_rule">Do Not Disturb was turned on by an automatic rule (<xliff:g name="rule">%s</xliff:g>).</string>
+    <!-- Prompt for when Priority mode is on from automatic rule in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_auto_rule" translatable="false">Priority mode was turned on by an automatic rule (<xliff:g name="rule">%s</xliff:g>).</string>
 
-    <!-- Prompt for when Do not disturb is on from app in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_app">Do Not Disturb was turned on by an app (<xliff:g name="app">%s</xliff:g>).</string>
+    <!-- Prompt for when Priority mode is on from app in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_app" translatable="false">Priority mode was turned on by an app (<xliff:g name="app">%s</xliff:g>).</string>
 
-    <!-- Prompt for when Do not disturb is on from automatic rule or app in QS [CHAR LIMIT=NONE] -->
-    <string name="qs_dnd_prompt_auto_rule_app">Do Not Disturb was turned on by an automatic rule or app.</string>
+    <!-- Prompt for when Priority mode is on from automatic rule or app in QS [CHAR LIMIT=NONE] -->
+    <string name="qs_dnd_prompt_auto_rule_app" translatable="false">Priority mode was turned on by an automatic rule or app.</string>
 
     <!-- Title of the "running foreground services" dialog. [CHAR LIMIT=NONE] -->
     <string name="running_foreground_services_title">Apps running in background</string>
@@ -2154,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>
@@ -2258,8 +2268,8 @@
     <string name="people_tile_description">See recent messages, missed calls, and status updates</string>
     <!-- Title text displayed for the Conversation widget [CHAR LIMIT=50] -->
     <string name="people_tile_title">Conversation</string>
-    <!-- Text when the Conversation widget when Do Not Disturb is suppressing the notification. [CHAR LIMIT=50] -->
-    <string name="paused_by_dnd">Paused by Do Not Disturb</string>
+    <!-- Text when the Conversation widget when Priority mode is suppressing the notification. [CHAR LIMIT=50] -->
+    <string name="paused_by_dnd" translatable="false">Paused by Priority mode</string>
     <!-- Content description text on the Conversation widget when a person has sent a new text message [CHAR LIMIT=150] -->
     <string name="new_notification_text_content_description"><xliff:g id="name" example="Anna">%1$s</xliff:g> sent a message: <xliff:g id="notification" example="Hey! How is your day going">%2$s</xliff:g></string>
     <!-- Content description text on the Conversation widget when a person has sent a new image message [CHAR LIMIT=150] -->
@@ -2354,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/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 1d2caf9..6345d11 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -275,23 +275,27 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("RegionSamplingHelper:");
-        pw.println("  sampleView isAttached: " + mSampledView.isAttachedToWindow());
-        pw.println("  sampleView isScValid: " + (mSampledView.isAttachedToWindow()
+        dump("", pw);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "RegionSamplingHelper:");
+        pw.println(prefix + "\tsampleView isAttached: " + mSampledView.isAttachedToWindow());
+        pw.println(prefix + "\tsampleView isScValid: " + (mSampledView.isAttachedToWindow()
                 ? mSampledView.getViewRootImpl().getSurfaceControl().isValid()
                 : "notAttached"));
-        pw.println("  mSamplingEnabled: " + mSamplingEnabled);
-        pw.println("  mSamplingListenerRegistered: " + mSamplingListenerRegistered);
-        pw.println("  mSamplingRequestBounds: " + mSamplingRequestBounds);
-        pw.println("  mRegisteredSamplingBounds: " + mRegisteredSamplingBounds);
-        pw.println("  mLastMedianLuma: " + mLastMedianLuma);
-        pw.println("  mCurrentMedianLuma: " + mCurrentMedianLuma);
-        pw.println("  mWindowVisible: " + mWindowVisible);
-        pw.println("  mWindowHasBlurs: " + mWindowHasBlurs);
-        pw.println("  mWaitingOnDraw: " + mWaitingOnDraw);
-        pw.println("  mRegisteredStopLayer: " + mRegisteredStopLayer);
-        pw.println("  mWrappedStopLayer: " + mWrappedStopLayer);
-        pw.println("  mIsDestroyed: " + mIsDestroyed);
+        pw.println(prefix + "\tmSamplingEnabled: " + mSamplingEnabled);
+        pw.println(prefix + "\tmSamplingListenerRegistered: " + mSamplingListenerRegistered);
+        pw.println(prefix + "\tmSamplingRequestBounds: " + mSamplingRequestBounds);
+        pw.println(prefix + "\tmRegisteredSamplingBounds: " + mRegisteredSamplingBounds);
+        pw.println(prefix + "\tmLastMedianLuma: " + mLastMedianLuma);
+        pw.println(prefix + "\tmCurrentMedianLuma: " + mCurrentMedianLuma);
+        pw.println(prefix + "\tmWindowVisible: " + mWindowVisible);
+        pw.println(prefix + "\tmWindowHasBlurs: " + mWindowHasBlurs);
+        pw.println(prefix + "\tmWaitingOnDraw: " + mWaitingOnDraw);
+        pw.println(prefix + "\tmRegisteredStopLayer: " + mRegisteredStopLayer);
+        pw.println(prefix + "\tmWrappedStopLayer: " + mWrappedStopLayer);
+        pw.println(prefix + "\tmIsDestroyed: " + mIsDestroyed);
     }
 
     public interface SamplingCallback {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index d518cb5..bb7a0a71 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -52,13 +52,14 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.view.RotationPolicy;
-import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.utilities.ViewRippler;
+import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
+import java.io.PrintWriter;
 import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -450,6 +451,30 @@
         return mDarkIconColor;
     }
 
+    public void dumpLogs(String prefix, PrintWriter pw) {
+        pw.println(prefix + "RotationButtonController:");
+
+        pw.println(String.format(
+                "%s\tmIsRecentsAnimationRunning=%b", prefix, mIsRecentsAnimationRunning));
+        pw.println(String.format("%s\tmHomeRotationEnabled=%b", prefix, mHomeRotationEnabled));
+        pw.println(String.format(
+                "%s\tmLastRotationSuggestion=%d", prefix, mLastRotationSuggestion));
+        pw.println(String.format(
+                "%s\tmPendingRotationSuggestion=%b", prefix, mPendingRotationSuggestion));
+        pw.println(String.format(
+                "%s\tmHoveringRotationSuggestion=%b", prefix, mHoveringRotationSuggestion));
+        pw.println(String.format("%s\tmListenersRegistered=%b", prefix, mListenersRegistered));
+        pw.println(String.format(
+                "%s\tmIsNavigationBarShowing=%b", prefix, mIsNavigationBarShowing));
+        pw.println(String.format("%s\tmBehavior=%d", prefix, mBehavior));
+        pw.println(String.format(
+                "%s\tmSkipOverrideUserLockPrefsOnce=%b", prefix, mSkipOverrideUserLockPrefsOnce));
+        pw.println(String.format(
+                "%s\tmLightIconColor=0x%s", prefix, Integer.toHexString(mLightIconColor)));
+        pw.println(String.format(
+                "%s\tmDarkIconColor=0x%s", prefix, Integer.toHexString(mDarkIconColor)));
+    }
+
     public RotationButton getRotationButton() {
         return mRotationButton;
     }
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 2ae32c7..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
@@ -25,6 +25,8 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -145,6 +147,11 @@
                                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
                             pipTask = taskInfo.token;
                         }
+                    } else if (change.getTaskInfo() != null
+                            && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_RECENTS) {
+                        // This task is for recents, keep it on top.
+                        t.setLayer(leashMap.get(change.getLeash()),
+                                info.getChanges().size() * 3 - i);
                     }
                 }
                 // Also make all the wallpapers opaque since we want the visible from the start
@@ -270,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();
@@ -310,53 +316,41 @@
                 return;
             }
             if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint);
-            try {
-                if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
-                    // The gesture went back to opening the app rather than continuing with
-                    // recents, so end the transition by moving the app back to the top (and also
-                    // re-showing it's task).
-                    final WindowContainerTransaction wct = new WindowContainerTransaction();
-                    final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                    for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                        // reverse order so that index 0 ends up on top
-                        wct.reorder(mPausingTasks.get(i), true /* onTop */);
-                        t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
-                    }
-                    mFinishCB.onTransitionFinished(wct, t);
-                } else {
-                    if (mOpeningLeashes != null) {
-                        // TODO: the launcher animation should handle this
-                        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                        for (int i = 0; i < mOpeningLeashes.size(); ++i) {
-                            t.show(mOpeningLeashes.get(i));
-                            t.setAlpha(mOpeningLeashes.get(i), 1.f);
-                        }
-                        t.apply();
-                    }
-                    if (mPipTask != null && mPipTransaction != null) {
-                        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                        t.show(mInfo.getChange(mPipTask).getLeash());
-                        PictureInPictureSurfaceTransaction.apply(mPipTransaction,
-                                mInfo.getChange(mPipTask).getLeash(), t);
-                        mPipTask = null;
-                        mPipTransaction = null;
-                        mFinishCB.onTransitionFinished(null /* wct */, t);
-                    } else {
-                        mFinishCB.onTransitionFinished(null /* wct */, null /* sct */);
-                    }
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            final WindowContainerTransaction wct;
 
+            if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
+                // The gesture went back to opening the app rather than continuing with
+                // recents, so end the transition by moving the app back to the top (and also
+                // re-showing it's task).
+                wct = new WindowContainerTransaction();
+                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+                    // reverse order so that index 0 ends up on top
+                    wct.reorder(mPausingTasks.get(i), true /* onTop */);
+                    t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
                 }
-            } catch (RemoteException e) {
-                Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
+            } else {
+                wct = null;
+                if (mPipTask != null && mPipTransaction != null) {
+                    t.show(mInfo.getChange(mPipTask).getLeash());
+                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
+                            mInfo.getChange(mPipTask).getLeash(), t);
+                    mPipTask = null;
+                    mPipTransaction = null;
+                }
             }
             // Release surface references now. This is apparently to free GPU
             // memory while doing quick operations (eg. during CTS).
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
             for (int i = 0; i < mLeashMap.size(); ++i) {
                 if (mLeashMap.keyAt(i) == mLeashMap.valueAt(i)) continue;
                 t.remove(mLeashMap.valueAt(i));
             }
-            t.apply();
+            try {
+                mFinishCB.onTransitionFinished(wct, t);
+            } catch (RemoteException e) {
+                Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
+                t.apply();
+            }
             for (int i = 0; i < mInfo.getChanges().size(); ++i) {
                 mInfo.getChanges().get(i).getLeash().release();
             }
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/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index e17f43e..409dc95 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -16,12 +16,14 @@
 package com.android.systemui.unfold
 
 import android.annotation.FloatRange
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 
 /**
  * Interface that allows to receive unfold transition progress updates.
+ *
  * It can be used to update view properties based on the current animation progress.
+ *
  * onTransitionProgress callback could be called on each frame.
  *
  * Use [createUnfoldTransitionProgressProvider] to create instances of this interface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
index 3f027e3..d1b0639 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -18,9 +18,8 @@
 import android.content.Context
 import android.os.SystemProperties
 
-internal class ResourceUnfoldTransitionConfig(
-    private val context: Context
-) : UnfoldTransitionConfig {
+internal class ResourceUnfoldTransitionConfig(private val context: Context) :
+    UnfoldTransitionConfig {
 
     override val isEnabled: Boolean
         get() = readIsEnabledResource() && isPropertyEnabled
@@ -29,19 +28,22 @@
         get() = readIsHingeAngleEnabled()
 
     private val isPropertyEnabled: Boolean
-        get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME,
-            UNFOLD_TRANSITION_PROPERTY_ENABLED) == UNFOLD_TRANSITION_PROPERTY_ENABLED
+        get() =
+            SystemProperties.getInt(
+                UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) ==
+                UNFOLD_TRANSITION_PROPERTY_ENABLED
 
-    private fun readIsEnabledResource(): Boolean = context.resources
-        .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
+    private fun readIsEnabledResource(): Boolean =
+        context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
 
-    private fun readIsHingeAngleEnabled(): Boolean = context.resources
-        .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
+    private fun readIsHingeAngleEnabled(): Boolean =
+        context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
 }
 
 /**
- * Temporary persistent property to control unfold transition mode
- * See [com.android.unfold.config.AnimationMode]
+ * Temporary persistent property to control unfold transition mode.
+ *
+ * See [com.android.unfold.config.AnimationMode].
  */
 private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled"
 private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 732882e..4c85b05 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -25,21 +25,17 @@
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 
-/**
- * Emits animation progress with fixed timing after unfolding
- */
+/** Emits animation progress with fixed timing after unfolding */
 internal class FixedTimingTransitionProgressProvider(
     private val foldStateProvider: FoldStateProvider
 ) : UnfoldTransitionProgressProvider, FoldStateProvider.FoldUpdatesListener {
 
     private val animatorListener = AnimatorListener()
     private val animator =
-        ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f)
-            .apply {
-                duration = TRANSITION_TIME_MILLIS
-                addListener(animatorListener)
-            }
-
+        ObjectAnimator.ofFloat(this, AnimationProgressProperty, 0f, 1f).apply {
+            duration = TRANSITION_TIME_MILLIS
+            addListener(animatorListener)
+        }
 
     private var transitionProgress: Float = 0.0f
         set(value) {
@@ -62,10 +58,8 @@
 
     override fun onFoldUpdate(@FoldUpdate update: Int) {
         when (update) {
-            FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE ->
-                animator.start()
-            FOLD_UPDATE_FINISH_CLOSED ->
-                animator.cancel()
+            FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start()
+            FOLD_UPDATE_FINISH_CLOSED -> animator.cancel()
         }
     }
 
@@ -77,16 +71,12 @@
         listeners.remove(listener)
     }
 
-    override fun onHingeAngleUpdate(angle: Float) {
-    }
+    override fun onHingeAngleUpdate(angle: Float) {}
 
     private object AnimationProgressProperty :
         FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") {
 
-        override fun setValue(
-            provider: FixedTimingTransitionProgressProvider,
-            value: Float
-        ) {
+        override fun setValue(provider: FixedTimingTransitionProgressProvider, value: Float) {
             provider.transitionProgress = value
         }
 
@@ -104,11 +94,9 @@
             listeners.forEach { it.onTransitionFinished() }
         }
 
-        override fun onAnimationRepeat(animator: Animator) {
-        }
+        override fun onAnimationRepeat(animator: Animator) {}
 
-        override fun onAnimationCancel(animator: Animator) {
-        }
+        override fun onAnimationCancel(animator: Animator) {}
     }
 
     private companion object {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index a701b44..5266f09 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -23,10 +23,10 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
 import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -35,13 +35,10 @@
 /** Maps fold updates to unfold transition progress using DynamicAnimation. */
 internal class PhysicsBasedUnfoldTransitionProgressProvider(
     private val foldStateProvider: FoldStateProvider
-) :
-    UnfoldTransitionProgressProvider,
-    FoldUpdatesListener,
-    DynamicAnimation.OnAnimationEndListener {
+) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
 
-    private val springAnimation = SpringAnimation(this, AnimationProgressProperty)
-        .apply {
+    private val springAnimation =
+        SpringAnimation(this, AnimationProgressProperty).apply {
             addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
         }
 
@@ -121,9 +118,7 @@
             isTransitionRunning = false
             springAnimation.cancel()
 
-            listeners.forEach {
-                it.onTransitionFinished()
-            }
+            listeners.forEach { it.onTransitionFinished() }
 
             if (DEBUG) {
                 Log.d(TAG, "onTransitionFinished")
@@ -143,9 +138,7 @@
     }
 
     private fun onStartTransition() {
-        listeners.forEach {
-            it.onTransitionStarted()
-        }
+        listeners.forEach { it.onTransitionStarted() }
         isTransitionRunning = true
 
         if (DEBUG) {
@@ -157,11 +150,12 @@
         if (!isTransitionRunning) onStartTransition()
 
         springAnimation.apply {
-            spring = SpringForce().apply {
-                finalPosition = startValue
-                dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
-                stiffness = SPRING_STIFFNESS
-            }
+            spring =
+                SpringForce().apply {
+                    finalPosition = startValue
+                    dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+                    stiffness = SPRING_STIFFNESS
+                }
             minimumVisibleChange = MINIMAL_VISIBLE_CHANGE
             setStartValue(startValue)
             setMinValue(0f)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 204ae09..24ecf87 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -45,11 +45,9 @@
 
     private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
 
-    @FoldUpdate
-    private var lastFoldUpdate: Int? = null
+    @FoldUpdate private var lastFoldUpdate: Int? = null
 
-    @FloatRange(from = 0.0, to = 180.0)
-    private var lastHingeAngle: Float = 0f
+    @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
 
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
@@ -60,10 +58,7 @@
     private var isUnfoldHandled = true
 
     override fun start() {
-        deviceStateManager.registerCallback(
-            mainExecutor,
-            foldStateListener
-        )
+        deviceStateManager.registerCallback(mainExecutor, foldStateListener)
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
     }
@@ -87,11 +82,14 @@
         get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
 
     private val isTransitionInProgess: Boolean
-        get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
+        get() =
+            lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
                 lastFoldUpdate == FOLD_UPDATE_START_CLOSING
 
     private fun onHingeAngle(angle: Float) {
-        if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
+        if (DEBUG) {
+            Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
+        }
 
         val isClosing = angle < lastHingeAngle
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
@@ -116,24 +114,28 @@
     }
 
     private inner class FoldStateListener(context: Context) :
-        DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
-            isFolded = folded
-            lastHingeAngle = FULLY_CLOSED_DEGREES
+        DeviceStateManager.FoldStateListener(
+            context,
+            { folded: Boolean ->
+                isFolded = folded
+                lastHingeAngle = FULLY_CLOSED_DEGREES
 
-            if (folded) {
-                hingeAngleProvider.stop()
-                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
-                cancelTimeout()
-                isUnfoldHandled = false
-            } else {
-                notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
-                rescheduleAbortAnimationTimeout()
-                hingeAngleProvider.start()
-            }
-        })
+                if (folded) {
+                    hingeAngleProvider.stop()
+                    notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+                    cancelTimeout()
+                    isUnfoldHandled = false
+                } else {
+                    notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+                    rescheduleAbortAnimationTimeout()
+                    hingeAngleProvider.start()
+                }
+            })
 
     private fun notifyFoldUpdate(@FoldUpdate update: Int) {
-        if (DEBUG) { Log.d(TAG, stateToString(update)) }
+        if (DEBUG) {
+            Log.d(TAG, stateToString(update))
+        }
         outputListeners.forEach { it.onFoldUpdate(update) }
         lastFoldUpdate = update
     }
@@ -149,8 +151,7 @@
         handler.removeCallbacks(timeoutRunnable)
     }
 
-    private inner class ScreenStatusListener :
-        ScreenStatusProvider.ScreenListener {
+    private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
 
         override fun onScreenTurnedOn() {
             // Trigger this event only if we are unfolded and this is the first screen
@@ -198,9 +199,7 @@
  * Time after which [FOLD_UPDATE_FINISH_HALF_OPEN] is emitted following a
  * [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
  */
-@VisibleForTesting
-const val HALF_OPENED_TIMEOUT_MILLIS = 1000L
+@VisibleForTesting const val HALF_OPENED_TIMEOUT_MILLIS = 1000L
 
 /** Threshold after which we consider the device fully unfolded. */
-@VisibleForTesting
-const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
+@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index df3563d..5495316 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -17,8 +17,8 @@
 
 import android.annotation.FloatRange
 import android.annotation.IntDef
-import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
 import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
 
 /**
  * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
@@ -35,14 +35,16 @@
         fun onFoldUpdate(@FoldUpdate update: Int)
     }
 
-    @IntDef(prefix = ["FOLD_UPDATE_"], value = [
-        FOLD_UPDATE_START_OPENING,
-        FOLD_UPDATE_START_CLOSING,
-        FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
-        FOLD_UPDATE_FINISH_HALF_OPEN,
-        FOLD_UPDATE_FINISH_FULL_OPEN,
-        FOLD_UPDATE_FINISH_CLOSED
-    ])
+    @IntDef(
+        prefix = ["FOLD_UPDATE_"],
+        value =
+            [
+                FOLD_UPDATE_START_OPENING,
+                FOLD_UPDATE_START_CLOSING,
+                FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
+                FOLD_UPDATE_FINISH_HALF_OPEN,
+                FOLD_UPDATE_FINISH_FULL_OPEN,
+                FOLD_UPDATE_FINISH_CLOSED])
     @Retention(AnnotationRetention.SOURCE)
     annotation class FoldUpdate
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
index 4ca1a53..b351585 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -3,15 +3,11 @@
 import androidx.core.util.Consumer
 
 internal object EmptyHingeAngleProvider : HingeAngleProvider {
-    override fun start() {
-    }
+    override fun start() {}
 
-    override fun stop() {
-    }
+    override fun stop() {}
 
-    override fun removeCallback(listener: Consumer<Float>) {
-    }
+    override fun removeCallback(listener: Consumer<Float>) {}
 
-    override fun addCallback(listener: Consumer<Float>) {
-    }
+    override fun addCallback(listener: Consumer<Float>) {}
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
index 6f52456..48a5b12 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
@@ -5,9 +5,10 @@
 
 /**
  * Emits device hinge angle values (angle between two integral parts of the device).
- * The hinge angle could be from 0 to 360 degrees inclusive.
- * For foldable devices usually 0 corresponds to fully closed (folded) state and
- * 180 degrees corresponds to fully open (flat) state
+ *
+ * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
+ * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
+ * state.
  */
 interface HingeAngleProvider : CallbackController<Consumer<Float>> {
     fun start()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index a42ebef..f6fe1ed 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -6,9 +6,8 @@
 import android.hardware.SensorManager
 import androidx.core.util.Consumer
 
-internal class HingeSensorAngleProvider(
-    private val sensorManager: SensorManager
-) : HingeAngleProvider {
+internal class HingeSensorAngleProvider(private val sensorManager: SensorManager) :
+    HingeAngleProvider {
 
     private val sensorListener = HingeAngleSensorListener()
     private val listeners: MutableList<Consumer<Float>> = arrayListOf()
@@ -32,8 +31,7 @@
 
     private inner class HingeAngleSensorListener : SensorEventListener {
 
-        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
-        }
+        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
 
         override fun onSensorChanged(event: SensorEvent) {
             listeners.forEach { it.accept(event.values[0]) }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index 1eec803..668c694 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.unfold.updates.screen
 
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
 import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
 
 interface ScreenStatusProvider : CallbackController<ScreenListener> {
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index 58d7dfb..53c528f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -10,9 +10,8 @@
 
 /**
  * [UnfoldTransitionProgressProvider] that emits transition progress only when the display has
- * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180).
- * It could be helpful to run the animation only when the display's rotation is perpendicular
- * to the fold.
+ * default rotation or 180 degrees opposite rotation (ROTATION_0 or ROTATION_180). It could be
+ * helpful to run the animation only when the display's rotation is perpendicular to the fold.
  */
 class NaturalRotationUnfoldProgressProvider(
     private val context: Context,
@@ -21,7 +20,7 @@
 ) : UnfoldTransitionProgressProvider {
 
     private val scopedUnfoldTransitionProgressProvider =
-            ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+        ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
     private val rotationWatcher = RotationWatcher()
 
     private var isNaturalRotation: Boolean = false
@@ -37,8 +36,8 @@
     }
 
     private fun onRotationChanged(rotation: Int) {
-        val isNewRotationNatural = rotation == Surface.ROTATION_0 ||
-                rotation == Surface.ROTATION_180
+        val isNewRotationNatural =
+            rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
         if (isNaturalRotation != isNewRotationNatural) {
             isNaturalRotation = isNewRotationNatural
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index ee79b87..dfe8792 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -21,17 +21,18 @@
     private val scopedUnfoldTransitionProgressProvider =
         ScopedUnfoldTransitionProgressProvider(progressProviderToWrap)
 
-    private val animatorDurationScaleObserver = object : ContentObserver(null) {
-        override fun onChange(selfChange: Boolean) {
-            onAnimatorScaleChanged()
+    private val animatorDurationScaleObserver =
+        object : ContentObserver(null) {
+            override fun onChange(selfChange: Boolean) {
+                onAnimatorScaleChanged()
+            }
         }
-    }
 
     init {
         contentResolver.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
-                /* notifyForDescendants= */ false,
-                animatorDurationScaleObserver)
+            Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+            /* notifyForDescendants= */ false,
+            animatorDurationScaleObserver)
         onAnimatorScaleChanged()
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index a274b74..7b67917 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -20,16 +20,18 @@
 
 /**
  * Manages progress listeners that can have smaller lifespan than the unfold animation.
- * Allows to limit getting transition updates to only when
- * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called
- * with readyToHandleTransition = true
  *
- * If the transition has already started by the moment when the clients are ready to play
- * the transition then it will report transition started callback and current animation progress.
+ * Allows to limit getting transition updates to only when
+ * [ScopedUnfoldTransitionProgressProvider.setReadyToHandleTransition] is called with
+ * readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play the
+ * transition then it will report transition started callback and current animation progress.
  */
-class ScopedUnfoldTransitionProgressProvider @JvmOverloads constructor(
-    source: UnfoldTransitionProgressProvider? = null
-) : UnfoldTransitionProgressProvider, TransitionProgressListener {
+class ScopedUnfoldTransitionProgressProvider
+@JvmOverloads
+constructor(source: UnfoldTransitionProgressProvider? = null) :
+    UnfoldTransitionProgressProvider, TransitionProgressListener {
 
     private var source: UnfoldTransitionProgressProvider? = null
 
@@ -43,8 +45,8 @@
         setSourceProvider(source)
     }
     /**
-     * Sets the source for the unfold transition progress updates,
-     * it replaces current provider if it is already set
+     * Sets the source for the unfold transition progress updates. Replaces current provider if it
+     * is already set
      * @param provider transition provider that emits transition progress updates
      */
     fun setSourceProvider(provider: UnfoldTransitionProgressProvider?) {
@@ -60,8 +62,10 @@
 
     /**
      * Allows to notify this provide whether the listeners can play the transition or not.
-     * Call this method with readyToHandleTransition = true when all listeners
-     * are ready to consume the transition progress events.
+     *
+     * Call this method with readyToHandleTransition = true when all listeners are ready to consume
+     * the transition progress events.
+     *
      * Call it with readyToHandleTransition = false when listeners can't process the events.
      */
     fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
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/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index 0340904..b2658c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import android.util.Log;
+
 /**
  * Defines constants for the Keyguard.
  */
@@ -25,7 +27,7 @@
      * Turns on debugging information for the whole Keyguard. This is very verbose and should only
      * be used temporarily for debugging.
      */
-    public static final boolean DEBUG = false;
+    public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG);
     public static final boolean DEBUG_SIM_STATES = true;
     public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 8c7ede2..848b8ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -262,7 +262,6 @@
         private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
         private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
         private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
-        private final Context mContext;
         private KeyguardClockSwitchController mKeyguardClockSwitchController;
         private View mClock;
         private int mUsableWidth;
@@ -286,7 +285,6 @@
                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
             mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
             setCancelable(false);
-            mContext = context;
         }
 
         @Override
@@ -311,7 +309,7 @@
 
             updateBounds();
 
-            setContentView(LayoutInflater.from(mContext)
+            setContentView(LayoutInflater.from(getContext())
                     .inflate(R.layout.keyguard_presentation, null));
 
             // Logic to make the lock screen fullscreen
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/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a348b42..f2d0427 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -37,8 +37,6 @@
 import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
@@ -104,8 +102,6 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -113,7 +109,6 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
-import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.RingerModeTracker;
 
 import com.google.android.collect.Lists;
@@ -338,7 +333,6 @@
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
     private SensorPrivacyManager mSensorPrivacyManager;
-    private FeatureFlags mFeatureFlags;
     private int mFaceAuthUserId;
 
     /**
@@ -443,7 +437,8 @@
     }
 
     @Override
-    public void onTrustChanged(boolean enabled, int userId, int flags) {
+    public void onTrustChanged(boolean enabled, int userId, int flags,
+            List<String> trustGrantedMessages) {
         Assert.isMainThread();
         boolean wasTrusted = mUserHasTrust.get(userId, false);
         mUserHasTrust.put(userId, enabled);
@@ -465,6 +460,19 @@
                 }
             }
         }
+
+        if (KeyguardUpdateMonitor.getCurrentUser() == userId && getUserHasTrust(userId)) {
+            CharSequence message = null;
+            if (trustGrantedMessages != null && trustGrantedMessages.size() > 0) {
+                message = trustGrantedMessages.get(0); // for now only shows the first in the list
+            }
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.showTrustGrantedMessage(message);
+                }
+            }
+        }
     }
 
     @Override
@@ -1790,8 +1798,7 @@
             AuthController authController,
             TelephonyListenerManager telephonyListenerManager,
             InteractionJankMonitor interactionJankMonitor,
-            LatencyTracker latencyTracker,
-            FeatureFlags featureFlags) {
+            LatencyTracker latencyTracker) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mTelephonyListenerManager = telephonyListenerManager;
@@ -1809,7 +1816,6 @@
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
-        mFeatureFlags = featureFlags;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2253,34 +2259,12 @@
             return;
         }
 
-        if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) {
-            // TODO (b/192405661): call new TrustManager API
-            mNumActiveUnlockTriggers++;
-            Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers);
-            showActiveUnlockNotification(mNumActiveUnlockTriggers);
+        if (shouldTriggerActiveUnlock()) {
+            mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser());
         }
     }
 
-    /**
-     * TODO (b/192405661): Only for testing. Remove before release.
-     */
-    private void showActiveUnlockNotification(int times) {
-        final String message = "Active unlock triggered "  + times + " times.";
-        final Notification.Builder nb =
-                new Notification.Builder(mContext, NotificationChannels.GENERAL)
-                        .setSmallIcon(R.drawable.ic_volume_ringer)
-                        .setContentTitle(message)
-                        .setStyle(new Notification.BigTextStyle().bigText(message));
-        mContext.getSystemService(NotificationManager.class).notifyAsUser(
-                "active_unlock",
-                0,
-                nb.build(),
-                UserHandle.ALL);
-    }
-
     private boolean shouldTriggerActiveUnlock() {
-        // TODO: check if active unlock is ENABLED / AVAILABLE
-
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
         final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep
@@ -2294,7 +2278,7 @@
         final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user)
                 || !mLockPatternUtils.isSecure(user);
 
-        // Don't trigger active unlock if fp is locked out TODO: confirm this one
+        // Don't trigger active unlock if fp is locked out
         final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
 
         // Don't trigger active unlock if primary auth is required
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index a74fd15..47e1035 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,6 +23,8 @@
 import android.telephony.TelephonyManager;
 import android.view.WindowManagerPolicyConstants;
 
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 
@@ -216,6 +218,11 @@
     public void onTrustGrantedWithFlags(int flags, int userId) { }
 
     /**
+     * Called when setting the trust granted message.
+     */
+    public void showTrustGrantedMessage(@Nullable CharSequence message) { }
+
+    /**
      * Called when a biometric has been acquired.
      * <p>
      * It is guaranteed that either {@link #onBiometricAuthenticated} or
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 2767904..b3be877 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -122,7 +122,8 @@
                     .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
                     .setCompatUI(Optional.of(mWMComponent.getCompatUI()))
-                    .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()));
+                    .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()))
+                    .setBackAnimation(mWMComponent.getBackAnimation());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
             // is separating this logic into newly creating SystemUITestsFactory.
@@ -142,7 +143,8 @@
                     .setTaskSurfaceHelper(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
                     .setCompatUI(Optional.ofNullable(null))
-                    .setDragAndDrop(Optional.ofNullable(null));
+                    .setDragAndDrop(Optional.ofNullable(null))
+                    .setBackAnimation(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
         if (mInitializeComponents) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 052ec86..dbd215d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -22,8 +22,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -54,7 +56,8 @@
  * The button icon is movable by dragging and it would not overlap navigation bar window.
  * And the button UI would automatically be dismissed after displaying for a period of time.
  */
-class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener {
+class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureListener,
+        ComponentCallbacks {
 
     @VisibleForTesting
     static final long FADING_ANIMATION_DURATION_MS = 300;
@@ -75,6 +78,7 @@
     private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
     private final LayoutParams mParams;
     private final SwitchListener mSwitchListener;
+    private final Configuration mConfiguration;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -101,6 +105,7 @@
     MagnificationModeSwitch(Context context, @NonNull ImageView imageView,
             SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SwitchListener switchListener) {
         mContext = context;
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
@@ -270,6 +275,7 @@
         mIsFadeOutAnimating = false;
         mImageView.setAlpha(0f);
         mWindowManager.removeView(mImageView);
+        mContext.unregisterComponentCallbacks(this);
         mIsVisible = false;
     }
 
@@ -291,6 +297,8 @@
             mImageView.setImageResource(getIconResId(mode));
         }
         if (!mIsVisible) {
+            onConfigurationChanged(mContext.getResources().getConfiguration());
+            mContext.registerComponentCallbacks(this);
             if (resetPosition) {
                 mDraggableWindowBounds.set(getDraggableWindowBounds());
                 mParams.x = mDraggableWindowBounds.right;
@@ -321,7 +329,21 @@
         }
     }
 
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        onConfigurationChanged(configDiff);
+    }
+
+    @Override
+    public void onLowMemory() {
+    }
+
     void onConfigurationChanged(int configDiff) {
+        if (configDiff == 0) {
+            return;
+        }
         if ((configDiff & (ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE))
                 != 0) {
             final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 4784bc1..885a177 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -23,7 +23,6 @@
 import android.annotation.MainThread;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
@@ -65,7 +64,6 @@
     private final OverviewProxyService mOverviewProxyService;
 
     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
-    private Configuration mLastConfiguration;
     private SysUiState mSysUiState;
 
     private static class ControllerSupplier extends
@@ -107,7 +105,6 @@
             SysUiState sysUiState, OverviewProxyService overviewProxyService) {
         super(context);
         mHandler = mainHandler;
-        mLastConfiguration = new Configuration(context.getResources().getConfiguration());
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         mCommandQueue = commandQueue;
         mModeSwitchesController = modeSwitchesController;
@@ -118,18 +115,6 @@
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        final int configDiff = newConfig.diff(mLastConfiguration);
-        mLastConfiguration.setTo(newConfig);
-        mMagnificationControllerSupplier.forEach(
-                magnificationController -> magnificationController.onConfigurationChanged(
-                        configDiff));
-        if (mModeSwitchesController != null) {
-            mModeSwitchesController.onConfigurationChanged(configDiff);
-        }
-    }
-
-    @Override
     public void start() {
         mCommandQueue.addCallback(this);
         mOverviewProxyService.addCallback(new OverviewProxyService.OverviewProxyListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index aa1a433..0d20403 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -76,7 +77,8 @@
  * Class to handle adding and removing a window magnification.
  */
 class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback,
-        MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener {
+        MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener,
+        ComponentCallbacks {
 
     private static final String TAG = "WindowMagnificationController";
     @SuppressWarnings("isloggabletaglength")
@@ -143,6 +145,7 @@
     private View mTopDrag;
     private View mRightDrag;
     private View mBottomDrag;
+    private final Configuration mConfiguration;
 
     @NonNull
     private final WindowMagnifierCallback mWindowMagnifierCallback;
@@ -191,6 +194,7 @@
         mSfVsyncFrameProvider = sfVsyncFrameProvider;
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
 
         final Display display = mContext.getDisplay();
         mDisplayId = mContext.getDisplayId();
@@ -339,6 +343,18 @@
         }
         mMirrorViewBounds.setEmpty();
         updateSystemUIStateIfNeeded();
+        mContext.unregisterComponentCallbacks(this);
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        onConfigurationChanged(configDiff);
+    }
+
+    @Override
+    public void onLowMemory() {
     }
 
     /**
@@ -351,6 +367,9 @@
             Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString(
                     configDiff));
         }
+        if (configDiff == 0) {
+            return;
+        }
         if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
             onRotate();
         }
@@ -390,7 +409,7 @@
 
         if (currentWindowBounds.equals(oldWindowBounds)) {
             if (DEBUG) {
-                Log.d(TAG, "updateMagnificationFrame -- window bounds is not changed");
+                Log.d(TAG, "handleScreenSizeChanged -- window bounds is not changed");
             }
             return false;
         }
@@ -851,6 +870,10 @@
             deleteWindowMagnification();
             return;
         }
+        if (!isWindowVisible()) {
+            onConfigurationChanged(mResources.getConfiguration());
+            mContext.registerComponentCallbacks(this);
+        }
 
         mMagnificationFrameOffsetX = Float.isNaN(magnificationFrameOffsetRatioX)
                 ? mMagnificationFrameOffsetX
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8b7aa09..3ece3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -224,7 +224,8 @@
 
         if (mUdfpsRequested && !getNotificationShadeVisible()
                 && (!mIsBouncerVisible
-                || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)) {
+                || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
+                && mKeyguardStateController.isShowing()) {
             return false;
         }
 
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/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 2598518..8367e11 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -244,7 +244,8 @@
             restoreFinishedReceiver,
             IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
             PERMISSION_SELF,
-            null
+            null,
+            Context.RECEIVER_NOT_EXPORTED
         )
         listingController.addCallback(listingCallback)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index b235692..bda8e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
@@ -121,6 +122,9 @@
         @BindsInstance
         Builder setDragAndDrop(Optional<DragAndDrop> d);
 
+        @BindsInstance
+        Builder setBackAnimation(Optional<BackAnimation> b);
+
         SysUIComponent build();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 154f6fa..bbe9dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -26,7 +26,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.clipboardoverlay.ClipboardListener;
 import com.android.systemui.dreams.DreamOverlayRegistrant;
-import com.android.systemui.dreams.appwidgets.ComplicationPrimer;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -212,11 +211,4 @@
     @ClassKey(DreamOverlayRegistrant.class)
     public abstract CoreStartable bindDreamOverlayRegistrant(
             DreamOverlayRegistrant dreamOverlayRegistrant);
-
-    /** Inject into AppWidgetOverlayPrimer. */
-    @Binds
-    @IntoMap
-    @ClassKey(ComplicationPrimer.class)
-    public abstract CoreStartable bindAppWidgetOverlayPrimer(
-            ComplicationPrimer complicationPrimer);
 }
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/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b815d4e..b926692 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -24,6 +24,7 @@
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.apppairs.AppPairs;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.dagger.TvWMShellModule;
@@ -123,4 +124,7 @@
 
     @WMSingleton
     DragAndDrop getDragAndDrop();
+
+    @WMSingleton
+    Optional<BackAnimation> getBackAnimation();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 3631938..63d4d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -163,16 +163,12 @@
 
             // Delay screen state transitions even longer while animations are running.
             boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
-                    && mParameters.shouldControlScreenOff() && !turningOn;
+                    && mParameters.shouldDelayDisplayDozeTransition() && !turningOn;
 
             // Delay screen state transition longer if UDFPS is actively authenticating a fp
             boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
                     && mUdfpsController != null && mUdfpsController.isFingerDown();
 
-            if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
-                mWakeLock.setAcquired(true);
-            }
-
             if (!messagePending) {
                 if (DEBUG) {
                     Log.d(TAG, "Display state changed to " + screenState + " delayed by "
@@ -180,6 +176,18 @@
                 }
 
                 if (shouldDelayTransitionEnteringDoze) {
+                    if (justInitialized) {
+                        // If we are delaying transitioning to doze and the display was not
+                        // turned on we set it to 'on' first to make sure that the animation
+                        // is visible before eventually moving it to doze state.
+                        // The display might be off at this point for example on foldable devices
+                        // when we switch displays and go to doze at the same time.
+                        applyScreenState(Display.STATE_ON);
+
+                        // Restore pending screen state as it gets cleared by 'applyScreenState'
+                        mPendingScreenState = screenState;
+                    }
+
                     mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
                 } else if (shouldDelayTransitionForUDFPS) {
                     mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
@@ -190,6 +198,10 @@
             } else if (DEBUG) {
                 Log.d(TAG, "Pending display state change to " + screenState);
             }
+
+            if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
+                mWakeLock.setAcquired(true);
+            }
         } else if (turningOff) {
             mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState));
         } else {
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/appwidgets/AppWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
deleted file mode 100644
index 687f7a2..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
+++ /dev/null
@@ -1,101 +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.appwidgets;
-
-import android.appwidget.AppWidgetHost;
-import android.appwidget.AppWidgetHostView;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * {@link AppWidgetProvider} is a singleton for accessing app widgets within SystemUI. This
- * consolidates resources such as the {@link AppWidgetHost} across potentially multiple
- * {@link ComplicationProvider} instances and other usages.
- */
-@SysUISingleton
-public class AppWidgetProvider {
-    private static final String TAG = "AppWidgetProvider";
-    public static final int APP_WIDGET_HOST_ID = 1025;
-
-    private final Context mContext;
-    private final AppWidgetManager mAppWidgetManager;
-    private final AppWidgetHost mAppWidgetHost;
-    private final Resources mResources;
-
-    @Inject
-    public AppWidgetProvider(Context context, @Main Resources resources) {
-        mContext = context;
-        mResources = resources;
-        mAppWidgetManager = android.appwidget.AppWidgetManager.getInstance(context);
-        mAppWidgetHost = new AppWidgetHost(context, APP_WIDGET_HOST_ID);
-        mAppWidgetHost.startListening();
-    }
-
-    /**
-     * Returns an {@link AppWidgetHostView} associated with a given {@link ComponentName}.
-     * @param component The {@link ComponentName} of the target {@link AppWidgetHostView}.
-     * @return The {@link AppWidgetHostView} or {@code null} on error.
-     */
-    public AppWidgetHostView getWidget(ComponentName component) {
-        final List<AppWidgetProviderInfo> appWidgetInfos =
-                mAppWidgetManager.getInstalledProviders();
-
-        for (AppWidgetProviderInfo widgetInfo : appWidgetInfos) {
-            if (widgetInfo.provider.equals(component)) {
-                final int widgetId = mAppWidgetHost.allocateAppWidgetId();
-
-                boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(widgetId,
-                        widgetInfo.provider);
-
-                if (!success) {
-                    Log.e(TAG, "could not bind to app widget:" + component);
-                    break;
-                }
-
-                final AppWidgetHostView appWidgetView =
-                        mAppWidgetHost.createView(mContext, widgetId, widgetInfo);
-
-                if (appWidgetView != null) {
-                    // Register a layout change listener to update the widget on any sizing changes.
-                    appWidgetView.addOnLayoutChangeListener(
-                            (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                                final float density = mResources.getDisplayMetrics().density;
-                                final int height = Math.round((bottom - top) / density);
-                                final int width = Math.round((right - left) / density);
-                                appWidgetView.updateAppWidgetSize(null, width, height,
-                                        width, height);
-                            });
-                }
-
-                return appWidgetView;
-            }
-        }
-
-        return null;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java
deleted file mode 100644
index 7d30faf..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationPrimer.java
+++ /dev/null
@@ -1,120 +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.appwidgets;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.Gravity;
-
-import androidx.constraintlayout.widget.ConstraintSet;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.ComplicationHostView;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent;
-
-import javax.inject.Inject;
-
-/**
- * {@link ComplicationPrimer} reads the configured AppWidget Complications from resources on start
- * and populates them into the {@link DreamOverlayStateController}.
- */
-public class ComplicationPrimer extends CoreStartable {
-    private final Resources mResources;
-    private final DreamOverlayStateController mDreamOverlayStateController;
-    private final AppWidgetComponent.Factory mComponentFactory;
-
-    @Inject
-    public ComplicationPrimer(Context context, @Main Resources resources,
-            DreamOverlayStateController overlayStateController,
-            AppWidgetComponent.Factory appWidgetOverlayFactory) {
-        super(context);
-        mResources = resources;
-        mDreamOverlayStateController = overlayStateController;
-        mComponentFactory = appWidgetOverlayFactory;
-    }
-
-    @Override
-    public void start() {
-    }
-
-    @Override
-    protected void onBootCompleted() {
-        super.onBootCompleted();
-        loadDefaultWidgets();
-    }
-
-    /**
-     * Generates the {@link ComplicationHostView.LayoutParams} for a given gravity. Default
-     * dimension constraints are also included in the params.
-     * @param gravity The gravity for the layout as defined by {@link Gravity}.
-     * @param resources The resourcs from which default dimensions will be extracted from.
-     * @return {@link ComplicationHostView.LayoutParams} representing the provided gravity and
-     *         default parameters.
-     */
-    private static ComplicationHostView.LayoutParams getLayoutParams(int gravity,
-            Resources resources) {
-        final ComplicationHostView.LayoutParams params = new ComplicationHostView.LayoutParams(
-                ComplicationHostView.LayoutParams.MATCH_CONSTRAINT,
-                ComplicationHostView.LayoutParams.MATCH_CONSTRAINT);
-
-        if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
-            params.bottomToBottom = ConstraintSet.PARENT_ID;
-        }
-
-        if ((gravity & Gravity.TOP) == Gravity.TOP) {
-            params.topToTop = ConstraintSet.PARENT_ID;
-        }
-
-        if ((gravity & Gravity.END) == Gravity.END) {
-            params.endToEnd = ConstraintSet.PARENT_ID;
-        }
-
-        if ((gravity & Gravity.START) == Gravity.START) {
-            params.startToStart = ConstraintSet.PARENT_ID;
-        }
-
-        // For now, apply the same sizing constraints on every widget.
-        params.matchConstraintPercentHeight =
-                resources.getFloat(R.dimen.config_dreamComplicationHeightPercent);
-        params.matchConstraintPercentWidth =
-                resources.getFloat(R.dimen.config_dreamComplicationWidthPercent);
-
-        return params;
-    }
-
-    /**
-     * Helper method for loading widgets based on configuration.
-     */
-    private void loadDefaultWidgets() {
-        final int[] positions = mResources.getIntArray(R.array.config_dreamComplicationPositions);
-        final String[] components =
-                mResources.getStringArray(R.array.config_dreamAppWidgetComplications);
-
-        for (int i = 0; i < Math.min(positions.length, components.length); i++) {
-            final AppWidgetComponent component = mComponentFactory.build(
-                    ComponentName.unflattenFromString(components[i]),
-                    getLayoutParams(positions[i], mResources));
-
-            mDreamOverlayStateController.addComplication(
-                    component.getAppWidgetComplicationProvider());
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java
deleted file mode 100644
index 9188ee5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/ComplicationProvider.java
+++ /dev/null
@@ -1,83 +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.appwidgets;
-
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
-import android.content.Context;
-import android.util.Log;
-import android.widget.RemoteViews;
-
-import com.android.systemui.dreams.ComplicationHost;
-import com.android.systemui.dreams.ComplicationHostView;
-import com.android.systemui.plugins.ActivityStarter;
-
-import javax.inject.Inject;
-
-/**
- * {@link ComplicationProvider} is an implementation of
- * {@link com.android.systemui.dreams.ComplicationProvider} for providing app widget-based
- * complications.
- */
-public class ComplicationProvider implements com.android.systemui.dreams.ComplicationProvider {
-    private static final String TAG = "AppWidgetCompProvider";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private final ActivityStarter mActivityStarter;
-    private final AppWidgetProvider mAppWidgetProvider;
-    private final ComponentName mComponentName;
-    private final ComplicationHostView.LayoutParams mLayoutParams;
-
-    @Inject
-    public ComplicationProvider(ActivityStarter activityStarter,
-            ComponentName componentName, AppWidgetProvider widgetProvider,
-            ComplicationHostView.LayoutParams layoutParams) {
-        mActivityStarter = activityStarter;
-        mComponentName = componentName;
-        mAppWidgetProvider = widgetProvider;
-        mLayoutParams = layoutParams;
-    }
-
-    @Override
-    public void onCreateComplication(Context context,
-            ComplicationHost.CreationCallback creationCallback,
-            ComplicationHost.InteractionCallback interactionCallback) {
-        final AppWidgetHostView widget = mAppWidgetProvider.getWidget(mComponentName);
-
-        if (widget == null) {
-            Log.e(TAG, "could not create widget");
-            return;
-        }
-
-        widget.setInteractionHandler((view, pendingIntent, response) -> {
-            if (pendingIntent.isActivity()) {
-                if (DEBUG) {
-                    Log.d(TAG, "launching pending intent from app widget:" + mComponentName);
-                }
-                interactionCallback.onExit();
-                mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent,
-                        null /*intentSentUiThreadCallback*/, view);
-                return true;
-            } else {
-                return RemoteViews.startPendingIntent(view, pendingIntent,
-                        response.getLaunchOptions(view));
-            }
-        });
-
-        creationCallback.onCreated(widget, mLayoutParams);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java
deleted file mode 100644
index 7beed17..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/dagger/AppWidgetComponent.java
+++ /dev/null
@@ -1,39 +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.appwidgets.dagger;
-
-import android.content.ComponentName;
-
-import com.android.systemui.dreams.ComplicationHostView;
-import com.android.systemui.dreams.appwidgets.ComplicationProvider;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/** */
-@Subcomponent
-public interface AppWidgetComponent {
-    /** */
-    @Subcomponent.Factory
-    interface Factory {
-        AppWidgetComponent build(@BindsInstance ComponentName component,
-                @BindsInstance ComplicationHostView.LayoutParams layoutParams);
-    }
-
-    /** Builds a {@link ComplicationProvider}. */
-    ComplicationProvider getAppWidgetComplicationProvider();
-}
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 0d4688e..d5053a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -16,15 +16,13 @@
 
 package com.android.systemui.dreams.dagger;
 
-import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent;
-
 import dagger.Module;
 
 /**
  * Dagger Module providing Communal-related functionality.
  */
 @Module(subcomponents = {
-        AppWidgetComponent.class,
-        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 5d6c2a2..e24df30 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -74,9 +74,6 @@
     public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER =
             new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher);
 
-    public static final ResourceBooleanFlag ACTIVE_UNLOCK =
-            new ResourceBooleanFlag(205, R.bool.flag_active_unlock);
-
     /***************************************/
     // 300 - power menu
     public static final BooleanFlag POWER_MENU_LITE =
@@ -88,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/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index b24d08d..3ae11ff 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -54,7 +54,7 @@
     private final View mRootView;
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
-                | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
+                    | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_ASSETS_PATHS);
     private final FragmentService mManager;
     private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager();
 
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/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index d190dcb..1bef32a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -141,6 +141,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
 
@@ -190,6 +191,7 @@
     private final Optional<Pip> mPipOptional;
     private final Optional<LegacySplitScreen> mSplitScreenOptional;
     private final Optional<Recents> mRecentsOptional;
+    private final Optional<BackAnimation> mBackAnimation;
     private final SystemActions mSystemActions;
     private final Handler mHandler;
     private final NavigationBarOverlayController mNavbarOverlayController;
@@ -504,7 +506,8 @@
             AutoHideController mainAutoHideController,
             AutoHideController.Factory autoHideControllerFactory,
             Optional<TelecomManager> telecomManagerOptional,
-            InputMethodManager inputMethodManager) {
+            InputMethodManager inputMethodManager,
+            Optional<BackAnimation> backAnimation) {
         mContext = context;
         mWindowManager = windowManager;
         mAccessibilityManager = accessibilityManager;
@@ -524,6 +527,7 @@
         mPipOptional = pipOptional;
         mSplitScreenOptional = splitScreenOptional;
         mRecentsOptional = recentsOptional;
+        mBackAnimation = backAnimation;
         mSystemActions = systemActions;
         mHandler = mainHandler;
         mNavbarOverlayController = navbarOverlayController;
@@ -629,6 +633,7 @@
 
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
         mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener);
+        mBackAnimation.ifPresent(mNavigationBarView::registerBackAnimation);
 
         prepareNavigationBarView();
         checkNavBarModes();
@@ -1680,6 +1685,7 @@
         private final AutoHideController.Factory mAutoHideControllerFactory;
         private final Optional<TelecomManager> mTelecomManagerOptional;
         private final InputMethodManager mInputMethodManager;
+        private final Optional<BackAnimation> mBackAnimation;
 
         @Inject
         public Factory(
@@ -1712,7 +1718,8 @@
                 AutoHideController mainAutoHideController,
                 AutoHideController.Factory autoHideControllerFactory,
                 Optional<TelecomManager> telecomManagerOptional,
-                InputMethodManager inputMethodManager) {
+                InputMethodManager inputMethodManager,
+                Optional<BackAnimation> backAnimation) {
             mAssistManagerLazy = assistManagerLazy;
             mAccessibilityManager = accessibilityManager;
             mDeviceProvisionedController = deviceProvisionedController;
@@ -1743,6 +1750,7 @@
             mAutoHideControllerFactory = autoHideControllerFactory;
             mTelecomManagerOptional = telecomManagerOptional;
             mInputMethodManager = inputMethodManager;
+            mBackAnimation = backAnimation;
         }
 
         /** Construct a {@link NavigationBar} */
@@ -1759,7 +1767,7 @@
                     mNavbarOverlayController, mUiEventLogger, mNavBarHelper,
                     mUserTracker, mMainLightBarController, mLightBarControllerFactory,
                     mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional,
-                    mInputMethodManager);
+                    mInputMethodManager, mBackAnimation);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index a984974..98b49b1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -59,6 +59,7 @@
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
 import java.io.FileDescriptor;
@@ -109,7 +110,8 @@
             DumpManager dumpManager,
             AutoHideController autoHideController,
             LightBarController lightBarController,
-            Optional<Pip> pipOptional) {
+            Optional<Pip> pipOptional,
+            Optional<BackAnimation> backAnimation) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarFactory = navigationBarFactory;
@@ -121,7 +123,8 @@
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
-                dumpManager, autoHideController, lightBarController, pipOptional);
+                dumpManager, autoHideController, lightBarController, pipOptional,
+                backAnimation.orElse(null));
         mIsTablet = isTablet(mContext);
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index ac816ba..2dd89f3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
 
@@ -1417,6 +1418,10 @@
         pip.removePipExclusionBoundsChangeListener(mPipListener);
     }
 
+    void registerBackAnimation(BackAnimation backAnimation) {
+        mEdgeBackGestureHandler.setBackAnimation(backAnimation);
+    }
+
     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
         pw.print("      " + caption + ": ");
         if (button == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 002dd10..441e79a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
 import java.io.FileDescriptor;
@@ -150,6 +151,7 @@
             mAutoHideController.touchAutoHide();
         }
     };
+    private BackAnimation mBackAnimation;
 
     @Inject
     public TaskbarDelegate(Context context) {
@@ -172,7 +174,8 @@
             SysUiState sysUiState, DumpManager dumpManager,
             AutoHideController autoHideController,
             LightBarController lightBarController,
-            Optional<Pip> pipOptional) {
+            Optional<Pip> pipOptional,
+            BackAnimation backAnimation) {
         // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mCommandQueue = commandQueue;
         mOverviewProxyService = overviewProxyService;
@@ -184,6 +187,7 @@
         mLightBarController = lightBarController;
         mLightBarTransitionsController = createLightBarTransitionsController();
         mPipOptional = pipOptional;
+        mBackAnimation = backAnimation;
     }
 
     // Separated into a method to keep setDependencies() clean/readable.
@@ -233,6 +237,7 @@
         mAutoHideController.setNavigationBar(mAutoHideUiElement);
         mLightBarController.setNavigationBar(mLightBarTransitionsController);
         mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
+        mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
         mInitialized = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index ab48a28..4f4bd1e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.provider.DeviceConfig;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -79,6 +80,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
+import com.android.wm.shell.back.BackAnimation;
 
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
@@ -231,6 +233,7 @@
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
 
     private NavigationEdgeBackPlugin mEdgeBackPlugin;
+    private BackAnimation mBackAnimation;
     private int mLeftInset;
     private int mRightInset;
     private int mSysUiFlags;
@@ -494,7 +497,7 @@
                     Choreographer.getInstance(), this::onInputEvent);
 
             // Add a nav bar panel window
-            setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
+            setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation));
             mPluginManager.addPluginListener(
                     this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
         }
@@ -509,7 +512,7 @@
 
     @Override
     public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
-        setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
+        setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation));
     }
 
     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -576,7 +579,9 @@
             mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
                     SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
             if (mBackGestureTfClassifierProvider.isActive()) {
+                Trace.beginSection("EdgeBackGestureHandler#loadVocab");
                 mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets());
+                Trace.endSection();
                 mUseMLModel = true;
                 return;
             }
@@ -930,6 +935,10 @@
         proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
     }
 
+    public void setBackAnimation(BackAnimation backAnimation) {
+        mBackAnimation = backAnimation;
+    }
+
     /**
      * Injectable instance to create a new EdgeBackGestureHandler.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 8d1dfc8..c18209d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -57,6 +57,7 @@
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.wm.shell.back.BackAnimation;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
@@ -277,11 +278,14 @@
                 }
             };
     private BackCallback mBackCallback;
+    private final BackAnimation mBackAnimation;
 
-    public NavigationBarEdgePanel(Context context) {
+    public NavigationBarEdgePanel(Context context,
+            BackAnimation backAnimation) {
         super(context);
 
         mWindowManager = context.getSystemService(WindowManager.class);
+        mBackAnimation = backAnimation;
         mVibratorHelper = Dependency.get(VibratorHelper.class);
 
         mDensity = context.getResources().getDisplayMetrics().density;
@@ -459,6 +463,9 @@
 
     @Override
     public void onMotionEvent(MotionEvent event) {
+        if (mBackAnimation != null) {
+            mBackAnimation.onBackMotion(event);
+        }
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
         }
@@ -866,6 +873,9 @@
             // Whenever the trigger back state changes the existing translation animation should be
             // cancelled
             mTranslationAnimation.cancel();
+            if (mBackAnimation != null) {
+                mBackAnimation.setTriggerBack(triggerBack);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index c8e2ca7..e26c768 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -93,6 +93,7 @@
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final UserTracker mUserTracker;
+    private final boolean mConfigEnableLockScreenButton;
 
     private HashMap<Integer, ContentObserver> mQRCodeScannerPreferenceObserver = new HashMap<>();
     private DeviceConfig.OnPropertiesChangedListener mOnDefaultQRCodeScannerChangedListener = null;
@@ -118,6 +119,9 @@
         mSecureSettings = secureSettings;
         mDeviceConfigProxy = proxy;
         mUserTracker = userTracker;
+
+        mConfigEnableLockScreenButton = mContext.getResources().getBoolean(
+            android.R.bool.config_enableQrCodeScannerOnLockScreen);
     }
 
     /**
@@ -156,7 +160,7 @@
      * Returns true if lock screen entry point for QR Code Scanner is to be enabled.
      */
     public boolean isEnabledForLockScreenButton() {
-        return mQRCodeScannerEnabled && mIntent != null;
+        return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton;
     }
 
     /**
@@ -235,6 +239,11 @@
     }
 
     private void updateQRCodeScannerPreferenceDetails(boolean updateSettings) {
+        if (!mConfigEnableLockScreenButton) {
+            // Settings only apply to lock screen entry point.
+            return;
+        }
+
         boolean prevQRCodeScannerEnabled = mQRCodeScannerEnabled;
         mQRCodeScannerEnabled = mSecureSettings.getIntForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0,
                 mUserTracker.getUserId()) != 0;
@@ -251,8 +260,15 @@
     private void updateQRCodeScannerActivityDetails() {
         String qrCodeScannerActivity = mDeviceConfigProxy.getString(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
-                mContext.getResources().getString(R.string.def_qr_code_component));
+                SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER, "");
+
+        // "" means either the flags is not available or is set to "", and in both the cases we
+        // want to use R.string.def_qr_code_component
+        if (Objects.equals(qrCodeScannerActivity, "")) {
+            qrCodeScannerActivity =
+                    mContext.getResources().getString(R.string.def_qr_code_component);
+        }
+
         String prevQrCodeScannerActivity = mQRCodeScannerActivity;
         ComponentName componentName = null;
         Intent intent = new Intent();
@@ -281,8 +297,12 @@
         // Our intent should always be explicit and should have a component set
         if (intent.getComponent() == null) return false;
 
-        int flags = PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_UNINSTALLED_PACKAGES
+                | PackageManager.MATCH_DISABLED_COMPONENTS
+                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
         return !mContext.getPackageManager().queryIntentActivities(intent,
                 flags).isEmpty();
     }
@@ -296,6 +316,11 @@
     }
 
     private void unregisterQRCodePreferenceObserver() {
+        if (!mConfigEnableLockScreenButton) {
+            // Settings only apply to lock screen entry point.
+            return;
+        }
+
         mQRCodeScannerPreferenceObserver.forEach((key, value) -> {
             mSecureSettings.unregisterContentObserver(value);
         });
@@ -357,6 +382,11 @@
     }
 
     private void registerQRCodePreferenceObserver() {
+        if (!mConfigEnableLockScreenButton) {
+            // Settings only apply to lock screen entry point.
+            return;
+        }
+
         int userId = mUserTracker.getUserId();
         if (mQRCodeScannerPreferenceObserver.getOrDefault(userId, null) != null) return;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index e10e4d8..7ac9205 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -56,7 +56,6 @@
  */
 class FooterActionsController @Inject constructor(
     view: FooterActionsView,
-    private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
     private val userTracker: UserTracker,
@@ -82,7 +81,6 @@
 
     private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
     private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
-    private val editButton: View = view.findViewById(android.R.id.edit)
     private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
 
     private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
@@ -176,13 +174,6 @@
             powerMenuLite.visibility = View.GONE
         }
         settingsButton.setOnClickListener(onClickListener)
-        editButton.setOnClickListener(View.OnClickListener { view: View? ->
-            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return@OnClickListener
-            }
-            activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) }
-        })
-
         updateView()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
index dd4dc87..7694be5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
@@ -36,7 +36,6 @@
 import javax.inject.Named
 
 class FooterActionsControllerBuilder @Inject constructor(
-    private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
     private val userTracker: UserTracker,
@@ -66,7 +65,7 @@
     }
 
     fun build(): FooterActionsController {
-        return FooterActionsController(view, qsPanelController, activityStarter, userManager,
+        return FooterActionsController(view, activityStarter, userManager,
                 userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
                 deviceProvisionedController, falsingManager, metricsLogger, tunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index f81f7bf..e6fa2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -43,7 +43,6 @@
     private lateinit var multiUserSwitch: MultiUserSwitch
     private lateinit var multiUserAvatar: ImageView
     private lateinit var tunerIcon: View
-    private lateinit var editTilesButton: View
 
     private var settingsCogAnimator: TouchAnimator? = null
 
@@ -52,7 +51,6 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        editTilesButton = requireViewById(android.R.id.edit)
         settingsButton = findViewById(R.id.settings_button)
         settingsContainer = findViewById(R.id.settings_button_container)
         multiUserSwitch = findViewById(R.id.multi_user_switch)
@@ -130,7 +128,6 @@
 
     private fun updateClickabilities() {
         multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
-        editTilesButton.isClickable = editTilesButton.visibility == VISIBLE
         settingsButton.isClickable = settingsButton.visibility == VISIBLE
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 066a286..4622660 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -47,6 +47,7 @@
     private PageIndicator mPageIndicator;
     private TextView mBuildText;
     private View mActionsContainer;
+    private View mEditButton;
 
     @Nullable
     protected TouchAnimator mFooterAnimator;
@@ -79,6 +80,7 @@
         mPageIndicator = findViewById(R.id.footer_page_indicator);
         mActionsContainer = requireViewById(R.id.qs_footer_actions);
         mBuildText = findViewById(R.id.build);
+        mEditButton = findViewById(android.R.id.edit);
 
         updateResources();
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -130,6 +132,7 @@
                 .addFloat(mActionsContainer, "alpha", 0, 1)
                 .addFloat(mPageIndicator, "alpha", 0, 1)
                 .addFloat(mBuildText, "alpha", 0, 1)
+                .addFloat(mEditButton, "alpha", 0, 1)
                 .setStartDelay(0.9f);
         return builder.build();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index e7c06e3..5327b7e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -26,6 +26,8 @@
 import android.widget.Toast;
 
 import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.ViewController;
@@ -45,10 +47,15 @@
     private final FooterActionsController mFooterActionsController;
     private final TextView mBuildText;
     private final PageIndicator mPageIndicator;
+    private final View mEditButton;
+    private final FalsingManager mFalsingManager;
+    private final ActivityStarter mActivityStarter;
 
     @Inject
     QSFooterViewController(QSFooterView view,
             UserTracker userTracker,
+            FalsingManager falsingManager,
+            ActivityStarter activityStarter,
             QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController,
             @Named(QS_FOOTER) FooterActionsController footerActionsController) {
@@ -57,9 +64,12 @@
         mQsPanelController = qsPanelController;
         mQuickQSPanelController = quickQSPanelController;
         mFooterActionsController = footerActionsController;
+        mFalsingManager = falsingManager;
+        mActivityStarter = activityStarter;
 
         mBuildText = mView.findViewById(R.id.build);
         mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
+        mEditButton = mView.findViewById(android.R.id.edit);
     }
 
     @Override
@@ -91,6 +101,14 @@
             }
             return false;
         });
+
+        mEditButton.setOnClickListener(view -> {
+            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                return;
+            }
+            mActivityStarter
+                    .postQSRunnableDismissingKeyguard(() -> mQsPanelController.showEdit(view));
+        });
         mQsPanelController.setFooterPageIndicator(mPageIndicator);
         mView.updateEverything();
     }
@@ -103,6 +121,7 @@
     @Override
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
+        mEditButton.setClickable(visibility == View.VISIBLE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 596d8f0..e2964ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -129,17 +129,27 @@
                     ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
                     : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
         } else if (uiMode == UiModeManager.MODE_NIGHT_CUSTOM) {
-            final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(mContext);
-            final LocalTime time;
-            if (nightMode) {
-                time = mUiModeManager.getCustomNightModeEnd();
+            int nightModeCustomType = mUiModeManager.getNightModeCustomType();
+            if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE) {
+                final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(
+                        mContext);
+                final LocalTime time;
+                if (nightMode) {
+                    time = mUiModeManager.getCustomNightModeEnd();
+                } else {
+                    time = mUiModeManager.getCustomNightModeStart();
+                }
+                state.secondaryLabel = mContext.getResources().getString(nightMode
+                                ? R.string.quick_settings_dark_mode_secondary_label_until
+                                : R.string.quick_settings_dark_mode_secondary_label_on_at,
+                        use24HourFormat ? time.toString() : formatter.format(time));
+            } else if (nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
+                state.secondaryLabel = mContext.getResources().getString(nightMode
+                        ? R.string.quick_settings_dark_mode_secondary_label_until_bedtime_ends
+                        : R.string.quick_settings_dark_mode_secondary_label_on_at_bedtime);
             } else {
-                time = mUiModeManager.getCustomNightModeStart();
+                state.secondaryLabel = null;
             }
-            state.secondaryLabel = mContext.getResources().getString(nightMode
-                    ? R.string.quick_settings_dark_mode_secondary_label_until
-                    : R.string.quick_settings_dark_mode_secondary_label_on_at,
-                    use24HourFormat ? time.toString() : formatter.format(time));
         } else {
             state.secondaryLabel = null;
         }
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/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index c8115e2..9a932ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -355,7 +355,8 @@
     }
 
     override fun onDraw(canvas: Canvas?) {
-        if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0) {
+        if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0
+            || revealAmount == 0f) {
             if (revealAmount < 1f) {
                 canvas?.drawColor(revealGradientEndColor)
             }
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/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 3449bd8..5aeab84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -23,7 +23,6 @@
 import android.os.Handler
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.NotificationListenerService.RankingMap
-import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.ConversationLayout
 import com.android.internal.widget.MessagingImageMessage
 import com.android.internal.widget.MessagingLayout
@@ -31,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -72,7 +72,8 @@
  */
 @SysUISingleton
 class AnimatedImageNotificationManager @Inject constructor(
-    private val notificationEntryManager: NotificationEntryManager,
+    private val notifCollection: CommonNotifCollection,
+    private val bindEventManager: BindEventManager,
     private val headsUpManager: HeadsUpManager,
     private val statusBarStateController: StatusBarStateController
 ) {
@@ -83,33 +84,23 @@
     fun bind() {
         headsUpManager.addListener(object : OnHeadsUpChangedListener {
             override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
-                entry.row?.let { row ->
-                    updateAnimatedImageDrawables(row, animating = isHeadsUp || isStatusBarExpanded)
-                }
+                updateAnimatedImageDrawables(entry)
             }
         })
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onExpandedChanged(isExpanded: Boolean) {
                 isStatusBarExpanded = isExpanded
-                notificationEntryManager.activeNotificationsForCurrentUser.forEach { entry ->
-                    entry.row?.let { row ->
-                        updateAnimatedImageDrawables(row, animating = isExpanded || row.isHeadsUp)
-                    }
-                }
+                notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables)
             }
         })
-        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
-            override fun onEntryInflated(entry: NotificationEntry) {
-                entry.row?.let { row ->
-                    updateAnimatedImageDrawables(
-                            row,
-                            animating = isStatusBarExpanded || row.isHeadsUp)
-                }
-            }
-            override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
-        })
+        bindEventManager.addListener(::updateAnimatedImageDrawables)
     }
 
+    private fun updateAnimatedImageDrawables(entry: NotificationEntry) =
+        entry.row?.let { row ->
+            updateAnimatedImageDrawables(row, animating = row.isHeadsUp || isStatusBarExpanded)
+        }
+
     private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) =
             (row.layouts?.asSequence() ?: emptySequence())
                     .flatMap { layout -> layout.allViews.asSequence() }
@@ -138,7 +129,7 @@
  */
 @SysUISingleton
 class ConversationNotificationManager @Inject constructor(
-    private val notificationEntryManager: NotificationEntryManager,
+    private val bindEventManager: BindEventManager,
     private val notificationGroupManager: NotificationGroupManagerLegacy,
     private val context: Context,
     private val notifCollection: CommonNotifCollection,
@@ -151,35 +142,12 @@
 
     private var notifPanelCollapsed = true
 
-    private val entryManagerListener = object : NotificationEntryListener {
-        override fun onNotificationRankingUpdated(rankingMap: RankingMap) =
-                updateNotificationRanking(rankingMap)
-        override fun onEntryInflated(entry: NotificationEntry) =
-                onEntryViewBound(entry)
-        override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
-        override fun onEntryRemoved(
-            entry: NotificationEntry,
-            visibility: NotificationVisibility?,
-            removedByUser: Boolean,
-            reason: Int
-        ) = removeTrackedEntry(entry)
-    }
-
-    private val notifCollectionListener = object : NotifCollectionListener {
-        override fun onRankingUpdate(ranking: RankingMap) =
-                updateNotificationRanking(ranking)
-
-        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-            removeTrackedEntry(entry)
-        }
-    }
-
     private fun updateNotificationRanking(rankingMap: RankingMap) {
         fun getLayouts(view: NotificationContentView) =
                 sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
         val ranking = Ranking()
         val activeConversationEntries = states.keys.asSequence()
-                .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
+                .mapNotNull { notifCollection.getEntry(it) }
         for (entry in activeConversationEntries) {
             if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
                 val important = ranking.channel.isImportantConversation
@@ -204,7 +172,7 @@
                                 layout.setIsImportantConversation(important, false)
                             }
                         }
-                if (changed) {
+                if (changed && !featureFlags.isNewPipelineEnabled()) {
                     notificationGroupManager.updateIsolation(entry)
                 }
             }
@@ -233,11 +201,14 @@
     }
 
     init {
-        if (featureFlags.isNewPipelineEnabled()) {
-            notifCollection.addCollectionListener(notifCollectionListener)
-        } else {
-            notificationEntryManager.addNotificationEntryListener(entryManagerListener)
-        }
+        notifCollection.addCollectionListener(object : NotifCollectionListener {
+            override fun onRankingUpdate(ranking: RankingMap) =
+                updateNotificationRanking(ranking)
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) =
+                removeTrackedEntry(entry)
+        })
+        bindEventManager.addListener(::onEntryViewBound)
     }
 
     private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) =
@@ -265,11 +236,10 @@
         val expanded = states
                 .asSequence()
                 .mapNotNull { (key, _) ->
-                    notificationEntryManager.getActiveNotificationUnfiltered(key)
-                            ?.let { entry ->
-                                if (entry.row?.isExpanded == true) key to entry
-                                else null
-                            }
+                    notifCollection.getEntry(key)?.let { entry ->
+                        if (entry.row?.isExpanded == true) key to entry
+                        else null
+                    }
                 }
                 .toMap()
         states.replaceAll { key, state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
new file mode 100644
index 0000000..03b978e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.notification
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A class which keeps track of whether section headers should be shown in the notification shade.
+ *
+ * (In an ideal world, this would directly monitor the state of the keyguard and invalidate the
+ * pipeline to show/hide headers, but the KeyguardController already invalidates the pipeline when
+ * the keyguard's state changes. Instead of having both classes monitor for state changes and ending
+ * up with duplicate runs of the pipeline, we let the KeyguardController update the header
+ * visibility when it invalidates, and we just store that state here.)
+ */
+@SysUISingleton
+class SectionHeaderVisibilityProvider @Inject constructor() {
+    var sectionHeadersVisible = true
+}
\ No newline at end of file
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/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 09ae7eb..87e531c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -260,7 +260,8 @@
         }
         events.sort(mEventComparator);
 
-        mLogger.logEmitBatch(batch.mGroupKey);
+        long batchAge = mClock.uptimeMillis() - batch.mCreatedTimestamp;
+        mLogger.logEmitBatch(batch.mGroupKey, batch.mMembers.size(), batchAge);
 
         mHandler.onNotificationBatchPosted(events);
     }
@@ -337,6 +338,6 @@
         void onNotificationBatchPosted(List<CoalescedEvent> events);
     }
 
-    private static final int MIN_GROUP_LINGER_DURATION = 50;
+    private static final int MIN_GROUP_LINGER_DURATION = 200;
     private static final int MAX_GROUP_LINGER_DURATION = 500;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
index d4d5b64..211e374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt
@@ -32,11 +32,13 @@
         })
     }
 
-    fun logEmitBatch(groupKey: String) {
+    fun logEmitBatch(groupKey: String, batchSize: Int, batchAgeMs: Long) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = groupKey
+            int1 = batchSize
+            long1 = batchAgeMs
         }, {
-            "Emitting event batch for group $str1"
+            "Emitting batch for group $str1 size=$int1 age=${long1}ms"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
index 3a39c39..f04b24e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
@@ -101,7 +101,7 @@
     };
 
     /**
-     * Puts foreground service notifications into its own section.
+     * Puts colorized foreground service and call notifications into its own section.
      */
     private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
             NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
@@ -109,12 +109,22 @@
         public boolean isInSection(ListEntry entry) {
             NotificationEntry notificationEntry = entry.getRepresentativeEntry();
             if (notificationEntry != null) {
-                Notification notification = notificationEntry.getSbn().getNotification();
-                return notification.isForegroundService()
-                        && notification.isColorized()
-                        && entry.getRepresentativeEntry().getImportance() > IMPORTANCE_MIN;
+                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
             }
             return false;
         }
+
+        private boolean isColorizedForegroundService(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return notification.isForegroundService()
+                    && notification.isColorized()
+                    && entry.getImportance() > IMPORTANCE_MIN;
+        }
+
+        private boolean isCall(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return entry.getImportance() > IMPORTANCE_MIN
+                    && notification.isStyle(Notification.CallStyle.class);
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index e59f4a6..ba88ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -20,11 +20,13 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.PeopleHeader
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
 import javax.inject.Inject
@@ -48,18 +50,36 @@
 
     val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
         override fun isInSection(entry: ListEntry): Boolean =
-                isConversation(entry.representativeEntry!!)
+                isConversation(entry)
         override fun getHeaderNodeController() =
                 // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
                 if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
     }
 
+    val comparator = object : NotifComparator("People") {
+        override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
+            assert(entry1.section === entry2.section)
+            if (entry1.section?.sectioner !== sectioner) {
+                return 0
+            }
+            val type1 = getPeopleType(entry1)
+            val type2 = getPeopleType(entry2)
+            return type2.compareTo(type1)
+        }
+    }
+
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addPromoter(notificationPromoter)
     }
 
-    private fun isConversation(entry: NotificationEntry): Boolean =
-        peopleNotificationIdentifier.getPeopleNotificationType(entry) != TYPE_NON_PERSON
+    private fun isConversation(entry: ListEntry): Boolean =
+        getPeopleType(entry) != TYPE_NON_PERSON
+
+    @PeopleNotificationType
+    private fun getPeopleType(entry: ListEntry): Int =
+        entry.representativeEntry?.let {
+            peopleNotificationIdentifier.getPeopleNotificationType(it)
+        } ?: TYPE_NON_PERSON
 
     companion object {
         private const val TAG = "ConversationCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
deleted file mode 100644
index 7410912..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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.systemui.statusbar.notification.collection.coordinator;
-
-import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;
-
-import android.util.ArraySet;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.render.NodeController;
-import com.android.systemui.statusbar.notification.dagger.IncomingHeader;
-import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Coordinates heads up notification (HUN) interactions with the notification pipeline based on
- * the HUN state reported by the {@link HeadsUpManager}. In this class we only consider one
- * notification, in particular the {@link HeadsUpManager#getTopEntry()}, to be HeadsUpping at a
- * time even though other notifications may be queued to heads up next.
- *
- * The current HUN, but not HUNs that are queued to heads up, will be:
- * - Lifetime extended until it's no longer heads upping.
- * - Promoted out of its group if it's a child of a group.
- * - In the HeadsUpCoordinatorSection. Ordering is configured in {@link NotifCoordinators}.
- * - Removed from HeadsUpManager if it's removed from the NotificationCollection.
- *
- * Note: The inflation callback in {@link PreparationCoordinator} handles showing HUNs.
- */
-@CoordinatorScope
-public class HeadsUpCoordinator implements Coordinator {
-    private static final String TAG = "HeadsUpCoordinator";
-
-    private final HeadsUpManager mHeadsUpManager;
-    private final HeadsUpViewBinder mHeadsUpViewBinder;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    private final NotificationRemoteInputManager mRemoteInputManager;
-    private final NodeController mIncomingHeaderController;
-    private final DelayableExecutor mExecutor;
-
-    private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
-    // notifs we've extended the lifetime for
-    private final ArraySet<NotificationEntry> mNotifsExtendingLifetime = new ArraySet<>();
-
-    @Inject
-    public HeadsUpCoordinator(
-            HeadsUpManager headsUpManager,
-            HeadsUpViewBinder headsUpViewBinder,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
-            NotificationRemoteInputManager remoteInputManager,
-            @IncomingHeader NodeController incomingHeaderController,
-            @Main DelayableExecutor executor) {
-        mHeadsUpManager = headsUpManager;
-        mHeadsUpViewBinder = headsUpViewBinder;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-        mRemoteInputManager = remoteInputManager;
-        mIncomingHeaderController = incomingHeaderController;
-        mExecutor = executor;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
-        pipeline.addCollectionListener(mNotifCollectionListener);
-        pipeline.addPromoter(mNotifPromoter);
-        pipeline.addNotificationLifetimeExtender(mLifetimeExtender);
-    }
-
-    public NotifSectioner getSectioner() {
-        return mNotifSectioner;
-    }
-
-    private void onHeadsUpViewBound(NotificationEntry entry) {
-        mHeadsUpManager.showNotification(entry);
-    }
-
-    private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
-        /**
-         * Notification was just added and if it should heads up, bind the view and then show it.
-         */
-        @Override
-        public void onEntryAdded(NotificationEntry entry) {
-            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
-                mHeadsUpViewBinder.bindHeadsUpView(
-                        entry,
-                        HeadsUpCoordinator.this::onHeadsUpViewBound);
-            }
-        }
-
-        /**
-         * Notification could've updated to be heads up or not heads up. Even if it did update to
-         * heads up, if the notification specified that it only wants to alert once, don't heads
-         * up again.
-         */
-        @Override
-        public void onEntryUpdated(NotificationEntry entry) {
-            boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
-            // includes check for whether this notification should be filtered:
-            boolean shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry);
-            final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
-            if (wasHeadsUp) {
-                if (shouldHeadsUp) {
-                    mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
-                } else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
-                    // We don't want this to be interrupting anymore, let's remove it
-                    mHeadsUpManager.removeNotification(
-                            entry.getKey(), false /* removeImmediately */);
-                }
-            } else if (shouldHeadsUp && hunAgain) {
-                // This notification was updated to be heads up, show it!
-                mHeadsUpViewBinder.bindHeadsUpView(
-                        entry,
-                        HeadsUpCoordinator.this::onHeadsUpViewBound);
-            }
-        }
-
-        /**
-         * Stop alerting HUNs that are removed from the notification collection
-         */
-        @Override
-        public void onEntryRemoved(NotificationEntry entry, int reason) {
-            final String entryKey = entry.getKey();
-            if (mHeadsUpManager.isAlerting(entryKey)) {
-                boolean removeImmediatelyForRemoteInput =
-                        mRemoteInputManager.isSpinning(entryKey)
-                                && !FORCE_REMOTE_INPUT_HISTORY;
-                mHeadsUpManager.removeNotification(entry.getKey(), removeImmediatelyForRemoteInput);
-            }
-        }
-
-        @Override
-        public void onEntryCleanUp(NotificationEntry entry) {
-            mHeadsUpViewBinder.abortBindCallback(entry);
-        }
-    };
-
-    private final NotifLifetimeExtender mLifetimeExtender = new NotifLifetimeExtender() {
-        @Override
-        public @NonNull String getName() {
-            return TAG;
-        }
-
-        @Override
-        public void setCallback(@NonNull OnEndLifetimeExtensionCallback callback) {
-            mEndLifetimeExtension = callback;
-        }
-
-        @Override
-        public boolean maybeExtendLifetime(@NonNull NotificationEntry entry, int reason) {
-            boolean extend = !mHeadsUpManager.canRemoveImmediately(entry.getKey());
-            if (extend) {
-                if (isSticky(entry)) {
-                    long removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.getKey());
-                    mExecutor.executeDelayed(() -> {
-                        if (mNotifsExtendingLifetime.contains(entry)
-                                && mHeadsUpManager.canRemoveImmediately(entry.getKey())) {
-                            mHeadsUpManager.removeNotification(
-                                    entry.getKey(), /* releaseImmediately */  true);
-                        }
-                    }, removeAfterMillis);
-                } else {
-                    // remove as early as possible
-                    mExecutor.execute(
-                            () -> mHeadsUpManager.removeNotification(
-                                    entry.getKey(), /* releaseImmediately */  false));
-                }
-                mNotifsExtendingLifetime.add(entry);
-            }
-            return extend;
-        }
-
-        @Override
-        public void cancelLifetimeExtension(@NonNull NotificationEntry entry) {
-            mNotifsExtendingLifetime.remove(entry);
-        }
-    };
-
-    private final NotifPromoter mNotifPromoter = new NotifPromoter(TAG) {
-        @Override
-        public boolean shouldPromoteToTopLevel(NotificationEntry entry) {
-            return isCurrentlyShowingHun(entry);
-        }
-    };
-
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("HeadsUp",
-            NotificationPriorityBucketKt.BUCKET_HEADS_UP) {
-        @Override
-        public boolean isInSection(ListEntry entry) {
-            return isCurrentlyShowingHun(entry);
-        }
-
-        @Nullable
-        @Override
-        public NodeController getHeaderNodeController() {
-            // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
-            if (RankingCoordinator.SHOW_ALL_SECTIONS) {
-                return mIncomingHeaderController;
-            }
-            return null;
-        }
-    };
-
-    private final OnHeadsUpChangedListener mOnHeadsUpChangedListener =
-            new OnHeadsUpChangedListener() {
-        @Override
-        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-            if (!isHeadsUp) {
-                mHeadsUpViewBinder.unbindHeadsUpView(entry);
-                endNotifLifetimeExtensionIfExtended(entry);
-            }
-        }
-    };
-
-    private boolean isSticky(NotificationEntry entry) {
-        return mHeadsUpManager.isSticky(entry.getKey());
-    }
-
-    private boolean isCurrentlyShowingHun(ListEntry entry) {
-        return mHeadsUpManager.isAlerting(entry.getKey());
-    }
-
-    private void endNotifLifetimeExtensionIfExtended(NotificationEntry entry) {
-        if (mNotifsExtendingLifetime.remove(entry)) {
-            mEndLifetimeExtension.onEndLifetimeExtension(mLifetimeExtender, entry);
-        }
-    }
-}
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
new file mode 100644
index 0000000..0bf21af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.util.ArraySet
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.interruption.HeadsUpController
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+/**
+ * Coordinates heads up notification (HUN) interactions with the notification pipeline based on
+ * the HUN state reported by the [HeadsUpManager]. In this class we only consider one
+ * notification, in particular the [HeadsUpManager.getTopEntry], to be HeadsUpping at a
+ * time even though other notifications may be queued to heads up next.
+ *
+ * The current HUN, but not HUNs that are queued to heads up, will be:
+ * - Lifetime extended until it's no longer heads upping.
+ * - Promoted out of its group if it's a child of a group.
+ * - In the HeadsUpCoordinatorSection. Ordering is configured in [NotifCoordinators].
+ * - Removed from HeadsUpManager if it's removed from the NotificationCollection.
+ *
+ * Note: The inflation callback in [PreparationCoordinator] handles showing HUNs.
+ */
+@CoordinatorScope
+class HeadsUpCoordinator @Inject constructor(
+    private val mHeadsUpManager: HeadsUpManager,
+    private val mHeadsUpViewBinder: HeadsUpViewBinder,
+    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
+    private val mRemoteInputManager: NotificationRemoteInputManager,
+    @IncomingHeader private val mIncomingHeaderController: NodeController,
+    @Main private val mExecutor: DelayableExecutor
+) : Coordinator {
+    private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
+
+    // notifs we've extended the lifetime for
+    private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>()
+
+    override fun attach(pipeline: NotifPipeline) {
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener)
+        pipeline.addCollectionListener(mNotifCollectionListener)
+        pipeline.addPromoter(mNotifPromoter)
+        pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
+    }
+
+    private fun onHeadsUpViewBound(entry: NotificationEntry) {
+        mHeadsUpManager.showNotification(entry)
+    }
+
+    private val mNotifCollectionListener = object : NotifCollectionListener {
+        /**
+         * Notification was just added and if it should heads up, bind the view and then show it.
+         */
+        override fun onEntryAdded(entry: NotificationEntry) {
+            if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) {
+                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
+            }
+        }
+
+        /**
+         * Notification could've updated to be heads up or not heads up. Even if it did update to
+         * heads up, if the notification specified that it only wants to alert once, don't heads
+         * up again.
+         */
+        override fun onEntryUpdated(entry: NotificationEntry) {
+            val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification)
+            // includes check for whether this notification should be filtered:
+            val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
+            val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key)
+            if (wasHeadsUp) {
+                if (shouldHeadsUp) {
+                    mHeadsUpManager.updateNotification(entry.key, hunAgain)
+                } else {
+                    // We don't want this to be interrupting anymore, let's remove it
+                    mHeadsUpManager.removeNotification(
+                        entry.key, false /* removeImmediately */
+                    )
+                }
+            } else if (shouldHeadsUp && hunAgain) {
+                // This notification was updated to be heads up, show it!
+                mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) }
+            }
+        }
+
+        /**
+         * Stop alerting HUNs that are removed from the notification collection
+         */
+        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+            val entryKey = entry.key
+            if (mHeadsUpManager.isAlerting(entryKey)) {
+                val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
+                        !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
+                mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
+            }
+        }
+
+        override fun onEntryCleanUp(entry: NotificationEntry) {
+            mHeadsUpViewBinder.abortBindCallback(entry)
+        }
+    }
+
+    private val mLifetimeExtender = object : NotifLifetimeExtender {
+        override fun getName() = TAG
+
+        override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
+            mEndLifetimeExtension = callback
+        }
+
+        override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
+            if (mHeadsUpManager.canRemoveImmediately(entry.key)) {
+                return false
+            }
+            if (isSticky(entry)) {
+                val removeAfterMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
+                mExecutor.executeDelayed({
+                    val canStillRemove = mHeadsUpManager.canRemoveImmediately(entry.key)
+                    if (mNotifsExtendingLifetime.contains(entry) && canStillRemove) {
+                        mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ true)
+                    }
+                }, removeAfterMillis)
+            } else {
+                mExecutor.execute {
+                    mHeadsUpManager.removeNotification(entry.key, /* releaseImmediately */ false)
+                }
+            }
+            mNotifsExtendingLifetime.add(entry)
+            return true
+        }
+
+        override fun cancelLifetimeExtension(entry: NotificationEntry) {
+            mNotifsExtendingLifetime.remove(entry)
+        }
+    }
+
+    private val mNotifPromoter = object : NotifPromoter(TAG) {
+        override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean =
+            isCurrentlyShowingHun(entry)
+    }
+
+    val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) {
+        override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry)
+
+        override fun getHeaderNodeController(): NodeController? =
+            // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController
+            if (RankingCoordinator.SHOW_ALL_SECTIONS) mIncomingHeaderController else null
+    }
+
+    private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener {
+        override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+            if (!isHeadsUp) {
+                mHeadsUpViewBinder.unbindHeadsUpView(entry)
+                endNotifLifetimeExtensionIfExtended(entry)
+            }
+        }
+    }
+
+    private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key)
+
+    private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key)
+
+    private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) {
+        if (mNotifsExtendingLifetime.remove(entry)) {
+            mEndLifetimeExtension?.onEndLifetimeExtension(mLifetimeExtender, entry)
+        }
+    }
+
+    companion object {
+        private const val TAG = "HeadsUpCoordinator"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 33005b3..733be9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -36,6 +36,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -48,7 +50,8 @@
 import javax.inject.Inject;
 
 /**
- * Filters low priority and privacy-sensitive notifications from the lockscreen.
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
  */
 @CoordinatorScope
 public class KeyguardCoordinator implements Coordinator {
@@ -62,6 +65,7 @@
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final HighPriorityProvider mHighPriorityProvider;
+    private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
 
     private boolean mHideSilentNotificationsOnLockscreen;
 
@@ -74,7 +78,8 @@
             BroadcastDispatcher broadcastDispatcher,
             StatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            HighPriorityProvider highPriorityProvider) {
+            HighPriorityProvider highPriorityProvider,
+            SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider) {
         mContext = context;
         mMainHandler = mainThreadHandler;
         mKeyguardStateController = keyguardStateController;
@@ -83,6 +88,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mHighPriorityProvider = highPriorityProvider;
+        mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
     }
 
     @Override
@@ -214,6 +220,8 @@
     }
 
     private void invalidateListFromFilter(String reason) {
+        mSectionHeaderVisibilityProvider.setSectionHeadersVisible(
+                mStatusBarStateController.getState() != StatusBarState.KEYGUARD);
         mNotifFilter.invalidateList();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 757fb5a..850cb4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import java.io.FileDescriptor
 import java.io.PrintWriter
@@ -63,6 +64,7 @@
 
     private val mCoordinators: MutableList<Coordinator> = ArrayList()
     private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()
+    private val mOrderedComparators: MutableList<NotifComparator> = ArrayList()
 
     /**
      * Creates all the coordinators.
@@ -117,6 +119,9 @@
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
         mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
         mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
+
+        // Manually add ordered comparators
+        mOrderedComparators.add(conversationCoordinator.comparator)
     }
 
     /**
@@ -128,6 +133,7 @@
             c.attach(pipeline)
         }
         pipeline.setSections(mOrderedSections)
+        pipeline.setComparators(mOrderedComparators)
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 195f367..35fe0ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -31,13 +31,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
@@ -99,7 +99,7 @@
 
     /** How long we can delay a group while waiting for all children to inflate */
     private final long mMaxGroupInflationDelay;
-    private final ConversationNotificationManager mConversationManager;
+    private final BindEventManagerImpl mBindEventManager;
 
     @Inject
     public PreparationCoordinator(
@@ -109,7 +109,7 @@
             NotifViewBarn viewBarn,
             NotifUiAdjustmentProvider adjustmentProvider,
             IStatusBarService service,
-            ConversationNotificationManager conversationManager) {
+            BindEventManagerImpl bindEventManager) {
         this(
                 logger,
                 notifInflater,
@@ -117,7 +117,7 @@
                 viewBarn,
                 adjustmentProvider,
                 service,
-                conversationManager,
+                bindEventManager,
                 CHILD_BIND_CUTOFF,
                 MAX_GROUP_INFLATION_DELAY);
     }
@@ -130,7 +130,7 @@
             NotifViewBarn viewBarn,
             NotifUiAdjustmentProvider adjustmentProvider,
             IStatusBarService service,
-            ConversationNotificationManager conversationManager,
+            BindEventManagerImpl bindEventManager,
             int childBindCutoff,
             long maxGroupInflationDelay) {
         mLogger = logger;
@@ -141,7 +141,7 @@
         mStatusBarService = service;
         mChildBindCutoff = childBindCutoff;
         mMaxGroupInflationDelay = maxGroupInflationDelay;
-        mConversationManager = conversationManager;
+        mBindEventManager = bindEventManager;
     }
 
     @Override
@@ -369,9 +369,7 @@
         mInflatingNotifs.remove(entry);
         mViewBarn.registerViewForEntry(entry, controller);
         mInflationStates.put(entry, STATE_INFLATED);
-        // NOTE: under the new pipeline there's no way to register for an inflation callback,
-        // so this one method is called by the PreparationCoordinator directly.
-        mConversationManager.onEntryViewBound(entry);
+        mBindEventManager.notifyViewBound(entry);
         mNotifInflatingFilter.invalidateList();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt
new file mode 100644
index 0000000..51bdd00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt
@@ -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.statusbar.notification.collection.inflation
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+
+/**
+ * Helper class that allows distributing bind events regardless of the pipeline.
+ *
+ * NOTE: This class isn't ideal; this exposes the concept of view inflation as something that can be
+ * globally registered for. This is built as it is to provide compatibility with patterns developed
+ * for the legacy pipeline. Ideally we'd have functionality that needs to know this information be
+ * handled by events that go through the ViewController itself.
+ */
+open class BindEventManager {
+    protected val listeners = ListenerSet<Listener>()
+
+    /** Register a listener */
+    fun addListener(listener: Listener) =
+        listeners.addIfAbsent(listener)
+
+    /** Deregister a listener */
+    fun removeListener(listener: Listener) =
+        listeners.remove(listener)
+
+    /** Listener interface for view bind events */
+    fun interface Listener {
+        fun onViewBound(entry: NotificationEntry)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
new file mode 100644
index 0000000..9d5b859
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt
@@ -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 com.android.systemui.statusbar.notification.collection.inflation
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.NotificationEntryListener
+import com.android.systemui.statusbar.notification.NotificationEntryManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener
+import javax.inject.Inject
+
+/**
+ * Helper class that allows distributing bind events regardless of the pipeline.
+ */
+@SysUISingleton
+class BindEventManagerImpl @Inject constructor() : BindEventManager() {
+    /** Emit the [Listener.onViewBound] event to all registered listeners. */
+    fun notifyViewBound(entry: NotificationEntry) =
+        listeners.forEach { listener -> listener.onViewBound(entry) }
+
+    /** Initialize this for the legacy pipeline. */
+    fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) {
+        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
+            override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry)
+            override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry)
+        })
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
index 0d150ed..f7bbd28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 
@@ -39,5 +41,5 @@
      * @return a negative integer, zero, or a positive integer as the first argument is less than
      *      equal to, or greater than the second (same as standard Comparator<> interface).
      */
-    public abstract int compare(ListEntry o1, ListEntry o2);
+    public abstract int compare(@NonNull ListEntry o1, @NonNull ListEntry o2);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
index d16d76a..ab777de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -77,7 +77,7 @@
         if (needsInitialization) {
             val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) }
             val permission = NOTIF_DEBUG_MODE_PERMISSION
-            context.registerReceiver(mReceiver, filter, permission, null)
+            context.registerReceiver(mReceiver, filter, permission, null, Context.RECEIVER_EXPORTED)
             Log.d(TAG, "Registered: $mReceiver")
         }
     }
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/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index f13470e..607500e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -35,6 +36,7 @@
 class NodeSpecBuilder(
     private val mediaContainerController: MediaContainerController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
+    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val viewBarn: NotifViewBarn
 ) {
     fun buildNodeSpec(
@@ -51,6 +53,7 @@
 
         var currentSection: NotifSection? = null
         val prevSections = mutableSetOf<NotifSection?>()
+        val showHeaders = sectionHeaderVisibilityProvider.sectionHeadersVisible
 
         for (entry in notifList) {
             val section = entry.section!!
@@ -61,7 +64,7 @@
 
             // If this notif begins a new section, first add the section's header view
             if (section != currentSection) {
-                if (section.headerController != currentSection?.headerController) {
+                if (section.headerController != currentSection?.headerController && showHeaders) {
                     section.headerController?.let { headerController ->
                         root.children.add(NodeSpecImpl(root, headerController))
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index a1800ed..4de8e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -40,6 +40,7 @@
 
     override fun addChildAt(child: NodeController, index: Int) {
         listContainer.addContainerViewAt(child.view, index)
+        listContainer.onNotificationViewUpdateFinished()
     }
 
     override fun moveChildTo(child: NodeController, index: Int) {
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/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index ad97392..4847072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.view.View
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -38,13 +39,15 @@
     @Assisted private val stackController: NotifStackController,
     mediaContainerController: MediaContainerController,
     featureManager: NotificationSectionsFeatureManager,
+    sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     logger: ShadeViewDifferLogger,
     private val viewBarn: NotifViewBarn
 ) {
     // We pass a shim view here because the listContainer may not actually have a view associated
     // with it and the differ never actually cares about the root node's view.
     private val rootController = RootNodeController(listContainer, View(context))
-    private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, viewBarn)
+    private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
+            sectionHeaderVisibilityProvider, viewBarn)
     private val viewDiffer = ShadeViewDiffer(rootController, logger)
 
     /** Method for attaching this manager to the pipeline. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index f1cba34..05c40b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -50,6 +50,8 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule;
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager;
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
@@ -358,5 +360,9 @@
 
     /** */
     @Binds
+    BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl);
+
+    /** */
+    @Binds
     NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 38f3c39..48f2daf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
 import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
@@ -76,6 +77,7 @@
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
+    private val bindEventManagerImpl: BindEventManagerImpl,
     private val remoteInputUriController: RemoteInputUriController,
     private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>,
     private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
@@ -131,6 +133,7 @@
             targetSdkResolver.initialize(entryManager)
             remoteInputUriController.attach(entryManager)
             groupAlertTransferHelper.bind(entryManager, groupManagerLegacy.get())
+            bindEventManagerImpl.attachToLegacyPipeline(entryManager)
             headsUpManager.addListener(groupManagerLegacy.get())
             headsUpManager.addListener(groupAlertTransferHelper)
             headsUpController.attach(entryManager, headsUpManager)
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 624e741..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.
@@ -552,14 +557,8 @@
         final ViewGroup transientContainer = getTransientContainer();
         if (parent == null || parent == newParent) {
             // If this view's current parent is null or the same as the new parent, the add will
-            // succeed, so just make sure the tracked transient container is in sync with the
-            // current parent.
-            if (transientContainer != null && transientContainer != parent) {
-                Log.w(TAG, "Expandable view " + this
-                        + " has transient container " + transientContainer
-                        + " but different parent" + parent);
-                setTransientContainer(null);
-            }
+            // succeed as long as it's a true child, so just make sure the view isn't transient.
+            removeFromTransientContainer();
             return;
         }
         if (transientContainer == null) {
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/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 1b42b58..d610b37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -305,6 +305,14 @@
     }
 
     /**
+     * When this method returns true then moving display state to power save mode will be
+     * delayed for a few seconds. This might be useful to play animations without reducing FPS.
+     */
+    public boolean shouldDelayDisplayDozeTransition() {
+        return mScreenOffAnimationController.shouldDelayDisplayDozeTransition();
+    }
+
+    /**
      * Whether we're capable of controlling the screen off animation if we want to. This isn't
      * possible if AOD isn't even enabled or if the flag is disabled.
      */
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/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 5d6e807..aff73e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -349,6 +349,9 @@
     }
 
     private void updateShelfIcons() {
+        if (mShelfIcons == null) {
+            return;
+        }
         updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
                 true /* showAmbient */,
                 true /* showLowPriority */,
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 0bc633c..769f689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -46,6 +46,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.Fragment;
 import android.app.StatusBarManager;
@@ -137,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;
@@ -211,8 +213,10 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -788,7 +792,8 @@
             Optional<SysUIUnfoldComponent> unfoldComponent,
             ControlsComponent controlsComponent,
             InteractionJankMonitor interactionJankMonitor,
-            QsFrameTranslateController qsFrameTranslateController) {
+            QsFrameTranslateController qsFrameTranslateController,
+            KeyguardUnlockAnimationController keyguardUnlockAnimationController) {
         super(view,
                 featureFlags,
                 falsingManager,
@@ -803,7 +808,8 @@
                 lockscreenGestureLogger,
                 panelExpansionStateManager,
                 ambientState,
-                interactionJankMonitor);
+                interactionJankMonitor,
+                keyguardUnlockAnimationController);
         mView = view;
         mVibratorHelper = vibratorHelper;
         mKeyguardMediaController = keyguardMediaController;
@@ -922,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() {
@@ -3321,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() {
@@ -4596,7 +4631,14 @@
 
             // Can affect multi-user switcher visibility as it depends on screen size by default:
             // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
-            reInflateViews();
+            boolean prevKeyguardUserSwitcherEnabled = mKeyguardUserSwitcherEnabled;
+            boolean prevKeyguardQsUserSwitchEnabled = mKeyguardQsUserSwitchEnabled;
+            updateUserSwitcherFlags();
+            if (prevKeyguardUserSwitcherEnabled != mKeyguardUserSwitcherEnabled
+                    || prevKeyguardQsUserSwitchEnabled != mKeyguardQsUserSwitchEnabled) {
+                reInflateViews();
+            }
+
             Trace.endSection();
         }
 
@@ -4913,33 +4955,53 @@
 
     private class DebugDrawable extends Drawable {
 
+        private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
+        private final Paint mDebugPaint = new Paint();
+
         @Override
-        public void draw(Canvas canvas) {
-            Paint p = new Paint();
-            p.setColor(Color.RED);
-            p.setStrokeWidth(2);
-            p.setStyle(Paint.Style.STROKE);
-            canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
-            p.setTextSize(24);
-            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, p);
-            p.setColor(Color.BLUE);
-            canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
-            p.setColor(Color.GREEN);
-            canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(),
-                    calculatePanelHeightQsExpanded(), p);
-            p.setColor(Color.YELLOW);
-            canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(),
-                    calculatePanelHeightShade(), p);
-            p.setColor(Color.MAGENTA);
-            canvas.drawLine(
-                    0, calculateNotificationsTopPadding(), mView.getWidth(),
-                    calculateNotificationsTopPadding(), p);
-            p.setColor(Color.CYAN);
+        public void draw(@NonNull Canvas canvas) {
+            mDebugTextUsedYPositions.clear();
+
+            mDebugPaint.setColor(Color.RED);
+            mDebugPaint.setStrokeWidth(2);
+            mDebugPaint.setStyle(Paint.Style.STROKE);
+            mDebugPaint.setTextSize(24);
+            if (mHeaderDebugInfo != null) canvas.drawText(mHeaderDebugInfo, 50, 100, mDebugPaint);
+
+            drawDebugInfo(canvas, getMaxPanelHeight(), Color.RED, "getMaxPanelHeight()");
+            drawDebugInfo(canvas, (int) getExpandedHeight(), Color.BLUE, "getExpandedHeight()");
+            drawDebugInfo(canvas, calculatePanelHeightQsExpanded(), Color.GREEN,
+                    "calculatePanelHeightQsExpanded()");
+            drawDebugInfo(canvas, calculatePanelHeightShade(), Color.YELLOW,
+                    "calculatePanelHeightShade()");
+            drawDebugInfo(canvas, (int) calculateNotificationsTopPadding(), Color.MAGENTA,
+                    "calculateNotificationsTopPadding()");
+            drawDebugInfo(canvas, mClockPositionResult.clockY, Color.GRAY,
+                    "mClockPositionResult.clockY");
+
+            mDebugPaint.setColor(Color.CYAN);
             canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScrollLayoutController.getTopPadding(), p);
-            p.setColor(Color.GRAY);
-            canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
-                    mClockPositionResult.clockY, p);
+                    mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
+        }
+
+        private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+            mDebugPaint.setColor(color);
+            canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
+                    /* stopY= */ y, mDebugPaint);
+            canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+        }
+
+        private int computeDebugYTextPosition(int lineY) {
+            if (lineY - mDebugPaint.getTextSize() < 0) {
+                // Avoiding drawing out of bounds
+                lineY += mDebugPaint.getTextSize();
+            }
+            int textY = lineY;
+            while (mDebugTextUsedYPositions.contains(textY)) {
+                textY = (int) (textY + mDebugPaint.getTextSize());
+            }
+            mDebugTextUsedYPositions.add(textY);
+            return textY;
         }
 
         @Override
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/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index e806ca0..091831f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -180,6 +180,14 @@
         animations.all { it.shouldAnimateDozingChange() }
 
     /**
+     * Returns true when moving display state to power save mode should be
+     * delayed for a few seconds. This might be useful to play animations in full quality,
+     * without reducing FPS.
+     */
+    fun shouldDelayDisplayDozeTransition(): Boolean =
+        animations.any { it.shouldDelayDisplayDozeTransition() }
+
+    /**
      * Return true to animate large <-> small clock transition
      */
     fun shouldAnimateClockChange(): Boolean =
@@ -207,6 +215,7 @@
     fun shouldHideScrimOnWakeUp(): Boolean = false
     fun overrideNotificationsDozeAmount(): Boolean = false
     fun shouldShowAodIconsWhenShade(): Boolean = false
+    fun shouldDelayDisplayDozeTransition(): Boolean = false
     fun shouldAnimateAodIcons(): Boolean = true
     fun shouldAnimateDozingChange(): Boolean = true
     fun shouldAnimateClockChange(): Boolean = true
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 a23e726e..48048b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -182,6 +182,9 @@
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
 
+    private float mAdditionalScrimBehindAlphaKeyguard = 0f;
+    // Combined scrim behind keyguard alpha of default scrim + additional scrim
+    // (if wallpaper dimming is applied).
     private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
     private final float mDefaultScrimAlpha;
 
@@ -437,7 +440,35 @@
         return mState;
     }
 
-    protected void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
+    /**
+     * Sets the additional scrim behind alpha keyguard that would be blended with the default scrim
+     * by applying alpha composition on both values.
+     *
+     * @param additionalScrimAlpha alpha value of additional scrim behind alpha keyguard.
+     */
+    protected void setAdditionalScrimBehindAlphaKeyguard(float additionalScrimAlpha) {
+        mAdditionalScrimBehindAlphaKeyguard = additionalScrimAlpha;
+    }
+
+    /**
+     * Applies alpha composition to the default scrim behind alpha keyguard and the additional
+     * scrim alpha, and sets this value to the scrim behind alpha keyguard.
+     * This is used to apply additional keyguard dimming on top of the default scrim alpha value.
+     */
+    protected void applyCompositeAlphaOnScrimBehindKeyguard() {
+        int compositeAlpha = ColorUtils.compositeAlpha(
+                (int) (255 * mAdditionalScrimBehindAlphaKeyguard),
+                (int) (255 * KEYGUARD_SCRIM_ALPHA));
+        float keyguardScrimAlpha = (float) compositeAlpha / 255;
+        setScrimBehindValues(keyguardScrimAlpha);
+    }
+
+    /**
+     * Sets the scrim behind alpha keyguard values. This is how much the keyguard will be dimmed.
+     *
+     * @param scrimBehindAlphaKeyguard alpha value of the scrim behind
+     */
+    private void setScrimBehindValues(float scrimBehindAlphaKeyguard) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
         ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
@@ -676,7 +707,10 @@
                     mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
                 } else {
                     mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-                    mNotificationsAlpha = mBehindAlpha;
+                    // Delay fade-in of notification scrim a bit further, to coincide with the
+                    // view fade in. Otherwise the empty panel can be quite jarring.
+                    mNotificationsAlpha = MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
+                            mPanelExpansionFraction);
                 }
                 mInFrontAlpha = 0;
             }
@@ -732,7 +766,7 @@
             }
             if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
                 // We're unoccluding the keyguard and don't want to have a bright flash.
-                mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+                mNotificationsAlpha = mScrimBehindAlphaKeyguard;
                 mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
             }
         }
@@ -775,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);
     }
 
@@ -1299,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/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8afa637..d2e1650 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -153,7 +153,7 @@
         // to make sure correct color is returned before "prepare" is called
         @Override
         public int getBehindTint() {
-            return Color.BLACK;
+            return DEBUG_MODE ? DEBUG_BEHIND_TINT : Color.BLACK;
         }
     },
 
@@ -264,6 +264,12 @@
         }
     };
 
+    private static final boolean DEBUG_MODE = false;
+
+    private static final int DEBUG_NOTIFICATIONS_TINT = Color.RED;
+    private static final int DEBUG_FRONT_TINT = Color.GREEN;
+    private static final int DEBUG_BEHIND_TINT = Color.BLUE;
+
     boolean mBlankScreen = false;
     long mAnimationDuration = ScrimController.ANIMATION_DURATION;
     int mFrontTint = Color.TRANSPARENT;
@@ -323,15 +329,15 @@
     }
 
     public int getFrontTint() {
-        return mFrontTint;
+        return DEBUG_MODE ? DEBUG_FRONT_TINT : mFrontTint;
     }
 
     public int getBehindTint() {
-        return mBehindTint;
+        return DEBUG_MODE ? DEBUG_BEHIND_TINT : mBehindTint;
     }
 
     public int getNotifTint() {
-        return mNotifTint;
+        return DEBUG_MODE ? DEBUG_NOTIFICATIONS_TINT : mNotifTint;
     }
 
     public long getAnimationDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index f8b0535..72237b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -123,7 +123,8 @@
                 + " canPanelBeCollapsed(): "
                 + getNotificationPanelViewController().canPanelBeCollapsed());
         if (getNotificationShadeWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()) {
+                && getNotificationPanelViewController().canPanelBeCollapsed()
+                && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
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 649aa42..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;
@@ -268,6 +267,7 @@
 
     // Should match the values in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+    public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
     static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
 
     private static final String BANNER_ACTION_CANCEL =
@@ -481,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;
@@ -618,6 +617,8 @@
     protected boolean mDozing;
     private boolean mIsFullscreen;
 
+    boolean mCloseQsBeforeScreenOff;
+
     private final NotificationMediaManager mMediaManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final NotificationRemoteInputManager mRemoteInputManager;
@@ -700,7 +701,6 @@
             KeyguardStateController keyguardStateController,
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
-            BypassHeadsUpNotifier bypassHeadsUpNotifier,
             FalsingManager falsingManager,
             FalsingCollector falsingCollector,
             BroadcastDispatcher broadcastDispatcher,
@@ -797,7 +797,6 @@
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -914,7 +913,6 @@
         mScreenLifecycle.addObserver(mScreenObserver);
         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
-        mBypassHeadsUpNotifier.setUp();
         if (mBubblesOptional.isPresent()) {
             mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
         }
@@ -1123,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;
+            }
         }
     }
 
@@ -2635,8 +2642,17 @@
                 if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
                     int flags = CommandQueue.FLAG_EXCLUDE_NONE;
                     String reason = intent.getStringExtra("reason");
-                    if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
-                        flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+                    if (reason != null) {
+                        if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
+                            flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+                        }
+                        // Do not collapse notifications when starting dreaming if the notifications
+                        // shade is used for the screen off animation. It might require expanded
+                        // state for the scrims to be visible
+                        if (reason.equals(SYSTEM_DIALOG_REASON_DREAM)
+                                && mScreenOffAnimationController.shouldExpandNotifications()) {
+                            flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
+                        }
                     }
                     mShadeController.animateCollapsePanels(flags);
                 }
@@ -2913,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;
 
@@ -2949,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;
@@ -2957,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();
@@ -3077,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.
 
@@ -3135,6 +3153,7 @@
         // bar.
         mKeyguardStateController.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
+        updateScrimController();
     }
 
     /**
@@ -3168,16 +3187,29 @@
      * Switches theme from light to dark and vice-versa.
      */
     protected void updateTheme() {
+        // Set additional scrim only if the lock and system wallpaper are different to prevent
+        // applying the dimming effect twice.
+        mUiBgExecutor.execute(() -> {
+            float dimAmount = 0f;
+            if (mWallpaperManager.lockScreenWallpaperExists()) {
+                dimAmount = mWallpaperManager.getWallpaperDimAmount();
+            }
+            final float scrimDimAmount = dimAmount;
+            mMainExecutor.execute(() -> {
+                mScrimController.setAdditionalScrimBehindAlphaKeyguard(scrimDimAmount);
+                mScrimController.applyCompositeAlphaOnScrimBehindKeyguard();
+            });
+        });
+
         // Lock wallpaper defines the color of the majority of the views, hence we'll use it
         // to set our default theme.
         final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
         final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
                 : R.style.Theme_SystemUI;
-        if (mContext.getThemeResId() == themeResId) {
-            return;
+        if (mContext.getThemeResId() != themeResId) {
+            mContext.setTheme(themeResId);
+            mConfigurationController.notifyThemeChanged();
         }
-        mContext.setTheme(themeResId);
-        mConfigurationController.notifyThemeChanged();
     }
 
     private void updateDozingState() {
@@ -3293,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
@@ -3541,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 -
@@ -3583,7 +3617,6 @@
         @Override
         public void onFinishedWakingUp() {
             mWakeUpCoordinator.setFullyAwake(true);
-            mBypassHeadsUpNotifier.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false);
             if (mLaunchCameraWhenFinishedWaking) {
                 mNotificationPanelViewController.launchCamera(
@@ -3631,6 +3664,10 @@
         public void onScreenTurnedOff() {
             mFalsingCollector.onScreenOff();
             mScrimController.onScreenTurnedOff();
+            if (mCloseQsBeforeScreenOff) {
+                mNotificationPanelViewController.closeQs();
+                mCloseQsBeforeScreenOff = false;
+            }
             updateIsKeyguard();
         }
     };
@@ -3723,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();
@@ -3745,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 ea0dd72..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
@@ -290,6 +300,9 @@
         return true
     }
 
+    override fun shouldDelayDisplayDozeTransition(): Boolean =
+        dozeParameters.get().shouldControlUnlockedScreenOff()
+
     fun addCallback(callback: Callback) {
         callbacks.add(callback)
     }
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/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 1030bfd..33f2140 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -127,10 +127,12 @@
         int rotationLockSetting =
                 mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
         if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
+            // This should not happen. Device states that have an ignored setting, should also
+            // specify a fallback device state which is not ignored.
             // We won't handle this device state. The same rotation lock setting as before should
             // apply and any changes to the rotation lock setting will be written for the previous
             // valid device state.
-            Log.v(TAG, "Ignoring new device state: " + state);
+            Log.w(TAG, "Missing fallback. Ignoring new device state: " + state);
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
index a418c74..bec5fc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java
@@ -52,12 +52,14 @@
     private final Handler mMainHandler = Handler.getMain();
     private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
     private SparseIntArray mDeviceStateRotationLockSettings;
+    private SparseIntArray mDeviceStateRotationLockFallbackSettings;
 
     private DeviceStateRotationLockSettingsManager(Context context) {
         mContentResolver = context.getContentResolver();
         mDeviceStateRotationLockDefaults =
                 context.getResources()
                         .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
+        loadDefaults();
         initializeInMemoryMap();
         listenForSettingsChange(context);
     }
@@ -114,6 +116,11 @@
 
     /** Updates the rotation lock setting for a specified device state. */
     public void updateSetting(int deviceState, boolean rotationLocked) {
+        if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) {
+            // The setting for this device state is IGNORED, and has a fallback device state.
+            // The setting for that fallback device state should be the changed in this case.
+            deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState);
+        }
         mDeviceStateRotationLockSettings.put(
                 deviceState,
                 rotationLocked
@@ -123,16 +130,37 @@
     }
 
     /**
-     * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting
-     * is specified for this device state, it will return {@link
+     * Returns the {@link Settings.Secure.DeviceStateRotationLockSetting} for the given device
+     * state.
+     *
+     * <p>If the setting for this device state is {@link DEVICE_STATE_ROTATION_LOCK_IGNORED}, it
+     * will return the setting for the fallback device state.
+     *
+     * <p>If no fallback is specified for this device state, it will return {@link
      * DEVICE_STATE_ROTATION_LOCK_IGNORED}.
      */
     @Settings.Secure.DeviceStateRotationLockSetting
     public int getRotationLockSetting(int deviceState) {
-        return mDeviceStateRotationLockSettings.get(
-                deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        int rotationLockSetting = mDeviceStateRotationLockSettings.get(
+                deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
+            rotationLockSetting = getFallbackRotationLockSetting(deviceState);
+        }
+        return rotationLockSetting;
     }
 
+    private int getFallbackRotationLockSetting(int deviceState) {
+        int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState);
+        if (indexOfFallbackState < 0) {
+            Log.w(TAG, "Setting is ignored, but no fallback was specified.");
+            return DEVICE_STATE_ROTATION_LOCK_IGNORED;
+        }
+        int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState);
+        return mDeviceStateRotationLockSettings.get(fallbackState,
+                /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
+    }
+
+
     /** Returns true if the rotation is locked for the current device state */
     public boolean isRotationLocked(int deviceState) {
         return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
@@ -223,21 +251,30 @@
     }
 
     private void loadDefaults() {
-        if (mDeviceStateRotationLockDefaults.length == 0) {
-            Log.w(TAG, "Empty default settings");
-            mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */ 0);
-            return;
-        }
-        mDeviceStateRotationLockSettings =
-                new SparseIntArray(mDeviceStateRotationLockDefaults.length);
-        for (String serializedDefault : mDeviceStateRotationLockDefaults) {
-            String[] entry = serializedDefault.split(SEPARATOR_REGEX);
+        mDeviceStateRotationLockSettings = new SparseIntArray(
+                mDeviceStateRotationLockDefaults.length);
+        mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
+        for (String entry : mDeviceStateRotationLockDefaults) {
+            String[] values = entry.split(SEPARATOR_REGEX);
             try {
-                int key = Integer.parseInt(entry[0]);
-                int value = Integer.parseInt(entry[1]);
-                mDeviceStateRotationLockSettings.put(key, value);
+                int deviceState = Integer.parseInt(values[0]);
+                int rotationLockSetting = Integer.parseInt(values[1]);
+                if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
+                    if (values.length == 3) {
+                        int fallbackDeviceState = Integer.parseInt(values[2]);
+                        mDeviceStateRotationLockFallbackSettings.put(deviceState,
+                                fallbackDeviceState);
+                    } else {
+                        Log.w(TAG,
+                                "Rotation lock setting is IGNORED, but values have unexpected "
+                                        + "size of "
+                                        + values.length);
+                    }
+                }
+                mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
             } catch (NumberFormatException e) {
-                Log.wtf(TAG, "Error deserializing default settings", e);
+                Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
+                return;
             }
         }
     }
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/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 3831857..59969c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -22,10 +22,14 @@
 
 import static com.android.settingslib.Utils.updateLocationEnabled;
 
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.location.LocationManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -68,31 +72,37 @@
     private final BootCompleteCache mBootCompleteCache;
     private final UserTracker mUserTracker;
     private final H mHandler;
-
+    private final Handler mBackgroundHandler;
+    private final PackageManager mPackageManager;
 
     private boolean mAreActiveLocationRequests;
     private boolean mShouldDisplayAllAccesses;
+    private boolean mShowSystemAccesses;
 
     @Inject
     public LocationControllerImpl(Context context, AppOpsController appOpsController,
             DeviceConfigProxy deviceConfigProxy,
             @Main Looper mainLooper, @Background Handler backgroundHandler,
             BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
-            UserTracker userTracker) {
+            UserTracker userTracker, PackageManager packageManager) {
         mContext = context;
         mAppOpsController = appOpsController;
         mDeviceConfigProxy = deviceConfigProxy;
         mBootCompleteCache = bootCompleteCache;
         mHandler = new H(mainLooper);
         mUserTracker = userTracker;
-        mShouldDisplayAllAccesses = getDeviceConfigSetting();
+        mBackgroundHandler = backgroundHandler;
+        mPackageManager = packageManager;
+        mShouldDisplayAllAccesses = getAllAccessesSetting();
+        mShowSystemAccesses = getShowSystemSetting();
 
         // Register to listen for changes in DeviceConfig settings.
         mDeviceConfigProxy.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_PRIVACY,
                 backgroundHandler::post,
                 properties -> {
-                    mShouldDisplayAllAccesses = getDeviceConfigSetting();
+                    mShouldDisplayAllAccesses = getAllAccessesSetting();
+                    mShowSystemAccesses = getShowSystemSetting();
                     updateActiveLocationRequests();
                 });
 
@@ -176,11 +186,15 @@
                 UserHandle.of(userId));
     }
 
-    private boolean getDeviceConfigSetting() {
+    private boolean getAllAccessesSetting() {
         return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
                 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false);
     }
 
+    private boolean getShowSystemSetting() {
+        return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false);
+    }
     /**
      * Returns true if there currently exist active high power location requests.
      */
@@ -202,35 +216,74 @@
      * Returns true if there currently exist active location requests.
      */
     @VisibleForTesting
-    protected boolean areActiveLocationRequests() {
+    protected void areActiveLocationRequests() {
         if (!mShouldDisplayAllAccesses) {
-            return false;
+            return;
         }
-        List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps();
+        boolean hadActiveLocationRequests = mAreActiveLocationRequests;
+        boolean shouldDisplay = false;
 
+        List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps();
+        final List<UserInfo> profiles = mUserTracker.getUserProfiles();
         final int numItems = appOpsItems.size();
         for (int i = 0; i < numItems; i++) {
             if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION
                     || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) {
-                return true;
+                if (mShowSystemAccesses) {
+                    shouldDisplay = true;
+                } else {
+                    shouldDisplay |= !isSystemApp(profiles, appOpsItems.get(i));
+                }
             }
         }
 
-        return false;
+        mAreActiveLocationRequests = areActiveHighPowerLocationRequests() || shouldDisplay;
+        if (mAreActiveLocationRequests != hadActiveLocationRequests) {
+            mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
+        }
+    }
+
+    private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) {
+        final String permission = AppOpsManager.opToPermission(item.getCode());
+        UserHandle user = UserHandle.getUserHandleForUid(item.getUid());
+
+        // Don't show apps belonging to background users except managed users.
+        boolean foundUser = false;
+        final int numProfiles = profiles.size();
+        for (int i = 0; i < numProfiles; i++) {
+            if (profiles.get(i).getUserHandle().equals(user)) {
+                foundUser = true;
+            }
+        }
+        if (!foundUser) {
+            return true;
+        }
+
+        final int permissionFlags = mPackageManager.getPermissionFlags(
+                permission, item.getPackageName(), user);
+        if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
+                PermissionChecker.PID_UNKNOWN, item.getUid(), item.getPackageName())
+                == PermissionChecker.PERMISSION_GRANTED) {
+            return (permissionFlags
+                    & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)
+                    == 0;
+        } else {
+            return (permissionFlags
+                    & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0;
+        }
     }
 
     // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION,
     // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary.
     private void updateActiveLocationRequests() {
-        boolean hadActiveLocationRequests = mAreActiveLocationRequests;
         if (mShouldDisplayAllAccesses) {
-            mAreActiveLocationRequests =
-                    areActiveHighPowerLocationRequests() || areActiveLocationRequests();
+            mBackgroundHandler.post(this::areActiveLocationRequests);
         } else {
+            boolean hadActiveLocationRequests = mAreActiveLocationRequests;
             mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
-        }
-        if (mAreActiveLocationRequests != hadActiveLocationRequests) {
-            mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
+            if (mAreActiveLocationRequests != hadActiveLocationRequests) {
+                mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 46fa20d..48949f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -51,6 +51,7 @@
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
@@ -665,8 +666,7 @@
             }
             // Hide soft-keyboard when the input view became invisible
             // (i.e. The notification shade collapsed by pressing the home key)
-            if (visibility != VISIBLE && !mEditText.isVisibleToUser()
-                    && !mController.isRemoteInputActive()) {
+            if (visibility != VISIBLE && !mController.isRemoteInputActive()) {
                 mEditText.hideIme();
             }
         }
@@ -779,8 +779,9 @@
         }
 
         private void hideIme() {
-            if (mInputMethodManager != null) {
-                mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+            final WindowInsetsController insetsController = getWindowInsetsController();
+            if (insetsController != null) {
+                insetsController.hide(WindowInsets.Type.ime());
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 52c416ba..aaf35af 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -30,17 +30,17 @@
 import javax.inject.Inject
 
 /**
- * Controls folding to AOD animation: when AOD is enabled and foldable device is folded
- * we play a special AOD animation on the outer screen
+ * Controls folding to AOD animation: when AOD is enabled and foldable device is folded we play a
+ * special AOD animation on the outer screen
  */
 @SysUIUnfoldScope
-class FoldAodAnimationController @Inject constructor(
+class FoldAodAnimationController
+@Inject
+constructor(
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
     private val globalSettings: GlobalSettings
-) : CallbackController<FoldAodAnimationStatus>,
-    ScreenOffAnimation,
-    WakefulnessLifecycle.Observer {
+) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
     private var alwaysOnEnabled: Boolean = false
     private var isScrimOpaque: Boolean = false
@@ -58,17 +58,13 @@
         wakefulnessLifecycle.addObserver(this)
     }
 
-    /**
-     * Returns true if we should run fold to AOD animation
-     */
-    override fun shouldPlayAnimation(): Boolean =
-        shouldPlayAnimation
+    /** Returns true if we should run fold to AOD animation */
+    override fun shouldPlayAnimation(): Boolean = shouldPlayAnimation
 
     override fun startAnimation(): Boolean =
         if (alwaysOnEnabled &&
             wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
-            globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
-        ) {
+            globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0") {
             shouldPlayAnimation = true
 
             isAnimationPlaying = true
@@ -107,9 +103,7 @@
         }
     }
 
-    /**
-     * Called when keyguard scrim opaque changed
-     */
+    /** Called when keyguard scrim opaque changed */
     override fun onScrimOpaqueChanged(isOpaque: Boolean) {
         isScrimOpaque = isOpaque
 
@@ -130,27 +124,21 @@
         }
     }
 
-    override fun isAnimationPlaying(): Boolean =
-        isAnimationPlaying
+    override fun isAnimationPlaying(): Boolean = isAnimationPlaying
 
-    override fun isKeyguardHideDelayed(): Boolean =
-        isAnimationPlaying()
+    override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying()
 
-    override fun shouldShowAodIconsWhenShade(): Boolean =
-        shouldPlayAnimation()
+    override fun shouldShowAodIconsWhenShade(): Boolean = shouldPlayAnimation()
 
-    override fun shouldAnimateAodIcons(): Boolean =
-        !shouldPlayAnimation()
+    override fun shouldAnimateAodIcons(): Boolean = !shouldPlayAnimation()
 
-    override fun shouldAnimateDozingChange(): Boolean =
-        !shouldPlayAnimation()
+    override fun shouldAnimateDozingChange(): Boolean = !shouldPlayAnimation()
 
-    override fun shouldAnimateClockChange(): Boolean =
-        !isAnimationPlaying()
+    override fun shouldAnimateClockChange(): Boolean = !isAnimationPlaying()
 
-    /**
-     * Called when AOD status is changed
-     */
+    override fun shouldDelayDisplayDozeTransition(): Boolean = shouldPlayAnimation()
+
+    /** Called when AOD status is changed */
     override fun onAlwaysOnChanged(alwaysOn: Boolean) {
         alwaysOnEnabled = alwaysOn
     }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 7f63d6c..79b42b8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -29,12 +29,16 @@
  * Logs performance metrics regarding time to turn the inner screen on.
  *
  * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn].
+ *
  * This should be used from only one process.
+ *
  * For now, the focus is on the time the inner display is visible, but in the future, it is easily
  * possible to monitor the time to go from the inner screen to the outer.
  */
 @SysUISingleton
-class UnfoldLatencyTracker @Inject constructor(
+class UnfoldLatencyTracker
+@Inject
+constructor(
     private val latencyTracker: LatencyTracker,
     private val deviceStateManager: DeviceStateManager,
     @UiBackground private val uiBgExecutor: Executor,
@@ -45,8 +49,11 @@
     private var folded: Boolean? = null
     private val foldStateListener = FoldStateListener(context)
     private val isFoldable: Boolean
-        get() = context.resources.getIntArray(
-            com.android.internal.R.array.config_foldedDeviceStates).isNotEmpty()
+        get() =
+            context
+                .resources
+                .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+                .isNotEmpty()
 
     /** Registers for relevant events only if the device is foldable. */
     fun init() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 0b89ef2..4b09a58 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -20,8 +20,8 @@
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
-import android.hardware.input.InputManager
 import android.hardware.display.DisplayManager
+import android.hardware.input.InputManager
 import android.os.Handler
 import android.os.Trace
 import android.view.Choreographer
@@ -46,7 +46,9 @@
 import javax.inject.Inject
 
 @SysUIUnfoldScope
-class UnfoldLightRevealOverlayAnimation @Inject constructor(
+class UnfoldLightRevealOverlayAnimation
+@Inject
+constructor(
     private val context: Context,
     private val deviceStateManager: DeviceStateManager,
     private val displayManager: DisplayManager,
@@ -75,12 +77,13 @@
         deviceStateManager.registerCallback(executor, FoldListener())
         unfoldTransitionProgressProvider.addCallback(transitionListener)
 
-        val containerBuilder = SurfaceControl.Builder(SurfaceSession())
-            .setContainerLayer()
-            .setName("unfold-overlay-container")
+        val containerBuilder =
+            SurfaceControl.Builder(SurfaceSession())
+                .setContainerLayer()
+                .setName("unfold-overlay-container")
 
-        displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY,
-            containerBuilder) { builder ->
+        displayAreaHelper.get().attachToRootDisplayArea(
+                Display.DEFAULT_DISPLAY, containerBuilder) { builder ->
             executor.execute {
                 overlayContainer = builder.build()
 
@@ -89,13 +92,13 @@
                     .show(overlayContainer)
                     .apply()
 
-                wwm = WindowlessWindowManager(context.resources.configuration,
-                    overlayContainer, null)
+                wwm =
+                    WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
             }
         }
 
-        displayManager.registerDisplayListener(displayListener, handler,
-            DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+        displayManager.registerDisplayListener(
+            displayListener, handler, DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
 
         // Get unfolded display size immediately as 'current display info' might be
         // not up-to-date during unfolding
@@ -136,8 +139,8 @@
         ensureOverlayRemoved()
 
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
-        val newView = LightRevealScrim(context, null)
-            .apply {
+        val newView =
+            LightRevealScrim(context, null).apply {
                 revealEffect = createLightRevealEffect()
                 isScrimOpaqueChangedListener = Consumer {}
                 revealAmount = 0f
@@ -147,8 +150,7 @@
         newRoot.setView(newView, params)
 
         onOverlayReady?.let { callback ->
-            Trace.beginAsyncSection(
-                "UnfoldLightRevealOverlayAnimation#relayout", 0)
+            Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
 
             newRoot.relayout(params) { transaction ->
                 val vsyncId = Choreographer.getSfInstance().vsyncId
@@ -161,15 +163,11 @@
                     // (turn on the brightness) only when the content is actually visible as it
                     // might be presented only in the next frame.
                     // See b/197538198
-                    transaction.setFrameTimelineVsync(vsyncId)
-                        .apply(/* sync */true)
+                    transaction.setFrameTimelineVsync(vsyncId).apply(/* sync */ true)
 
-                    transaction
-                        .setFrameTimelineVsync(vsyncId + 1)
-                        .apply(/* sync */ true)
+                    transaction.setFrameTimelineVsync(vsyncId + 1).apply(/* sync */ true)
 
-                    Trace.endAsyncSection(
-                        "UnfoldLightRevealOverlayAnimation#relayout", 0)
+                    Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
                     callback.run()
                 }
             }
@@ -185,10 +183,10 @@
         val rotation = context.display!!.rotation
         val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
 
-        params.height = if (isNatural)
-            unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
-        params.width = if (isNatural)
-            unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
+        params.height =
+            if (isNatural) unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
+        params.width =
+            if (isNatural) unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
 
         params.format = PixelFormat.TRANSLUCENT
         params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
@@ -206,8 +204,8 @@
     }
 
     private fun createLightRevealEffect(): LightRevealEffect {
-        val isVerticalFold = currentRotation == Surface.ROTATION_0 ||
-            currentRotation == Surface.ROTATION_180
+        val isVerticalFold =
+            currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
         return LinearLightRevealEffect(isVertical = isVerticalFold)
     }
 
@@ -218,7 +216,8 @@
     }
 
     private fun getUnfoldedDisplayInfo(): DisplayInfo =
-        displayManager.displays
+        displayManager
+            .displays
             .asSequence()
             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
             .filter { it.type == Display.TYPE_INTERNAL }
@@ -255,18 +254,19 @@
             }
         }
 
-        override fun onDisplayAdded(displayId: Int) {
-        }
+        override fun onDisplayAdded(displayId: Int) {}
 
-        override fun onDisplayRemoved(displayId: Int) {
-        }
+        override fun onDisplayRemoved(displayId: Int) {}
     }
 
-    private inner class FoldListener : FoldStateListener(context, Consumer { isFolded ->
-        if (isFolded) {
-            ensureOverlayRemoved()
-            isUnfoldHandled = false
-        }
-        this.isFolded = isFolded
-    })
+    private inner class FoldListener :
+        FoldStateListener(
+            context,
+            Consumer { isFolded ->
+                if (isFolded) {
+                    ensureOverlayRemoved()
+                    isUnfoldHandled = false
+                }
+                this.isFolded = isFolded
+            })
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
index bd04ad8..2325acf 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
@@ -21,29 +21,23 @@
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener
 import java.util.concurrent.Executor
 
-class UnfoldProgressProvider(
-    private val unfoldProgressProvider: UnfoldTransitionProgressProvider
-) : ShellUnfoldProgressProvider {
+class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) :
+    ShellUnfoldProgressProvider {
 
     override fun addListener(executor: Executor, listener: UnfoldListener) {
-        unfoldProgressProvider.addCallback(object : TransitionProgressListener {
-            override fun onTransitionStarted() {
-                executor.execute {
-                    listener.onStateChangeStarted()
+        unfoldProgressProvider.addCallback(
+            object : TransitionProgressListener {
+                override fun onTransitionStarted() {
+                    executor.execute { listener.onStateChangeStarted() }
                 }
-            }
 
-            override fun onTransitionProgress(progress: Float) {
-                executor.execute {
-                    listener.onStateChangeProgress(progress)
+                override fun onTransitionProgress(progress: Float) {
+                    executor.execute { listener.onStateChangeProgress(progress) }
                 }
-            }
 
-            override fun onTransitionFinished() {
-                executor.execute {
-                    listener.onStateChangeFinished()
+                override fun onTransitionFinished() {
+                    executor.execute { listener.onStateChangeFinished() }
                 }
-            }
-        })
+            })
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 178d014..d2d2361 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -90,7 +90,7 @@
         config: UnfoldTransitionConfig,
         provider: Optional<UnfoldTransitionProgressProvider>
     ): ShellUnfoldProgressProvider =
-        if (config.isEnabled && provider.isPresent()) {
+        if (config.isEnabled && provider.isPresent) {
             UnfoldProgressProvider(provider.get())
         } else {
             ShellUnfoldProgressProvider.NO_PROVIDER
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
index a184315..d723760 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt
@@ -21,7 +21,9 @@
 import javax.inject.Inject
 
 @SysUIUnfoldScope
-class UnfoldTransitionWallpaperController @Inject constructor(
+class UnfoldTransitionWallpaperController
+@Inject
+constructor(
     private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
     private val wallpaperController: WallpaperController
 ) {
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/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7266e41..08d881f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -88,7 +88,6 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -108,6 +107,7 @@
 import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -174,10 +174,10 @@
     private InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private LatencyTracker mLatencyTracker;
-    @Mock
-    private FeatureFlags mFeatureFlags;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
+    @Mock
+    private KeyguardUpdateMonitorCallback mTestCallback;
     // Direct executor
     private Executor mBackgroundExecutor = Runnable::run;
     private Executor mMainExecutor = Runnable::run;
@@ -255,11 +255,13 @@
 
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
         mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
+        mKeyguardUpdateMonitor.registerCallback(mTestCallback);
     }
 
     @After
     public void tearDown() {
         mMockitoSession.finishMocking();
+        mKeyguardUpdateMonitor.removeCallback(mTestCallback);
         mKeyguardUpdateMonitor.destroy();
     }
 
@@ -599,7 +601,8 @@
         mTestableLooper.processAllMessages();
         when(mKeyguardBypassController.canBypass()).thenReturn(true);
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */);
+                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                new ArrayList<>());
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
         verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
@@ -609,7 +612,7 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */);
+                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
@@ -754,7 +757,8 @@
     @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
+        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */,
+                new ArrayList<>());
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
@@ -985,7 +989,7 @@
 
         // WHEN trust is enabled (ie: via smartlock)
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
-                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */);
+                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
 
         // THEN we shouldn't listen for udfps
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1069,6 +1073,17 @@
                 anyBoolean());
     }
 
+    @Test
+    public void testShowTrustGrantedMessage_onTrustGranted() {
+        // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string
+        mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
+                KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */,
+                Arrays.asList("Unlocked by wearable"));
+
+        // THEN the showTrustGrantedMessage should be called with the first message
+        verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
+    }
+
     private void setKeyguardBouncerVisibility(boolean isVisible) {
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible);
         mTestableLooper.processAllMessages();
@@ -1108,7 +1123,7 @@
                     mRingerModeTracker, mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
-                    mInteractionJankMonitor, mLatencyTracker, mFeatureFlags);
+                    mInteractionJankMonitor, mLatencyTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 6ddfbb2..bc89da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -111,6 +111,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mContext = Mockito.spy(getContext());
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         mSwitchListener = new SwitchListenerStub();
         mWindowManager = spy(new TestableWindowManager(wm));
@@ -139,16 +140,18 @@
     public void tearDown() {
         mFadeOutAnimation = null;
         mMotionEventHelper.recycleEvents();
+        mMagnificationModeSwitch.removeButton();
     }
 
     @Test
-    public void removeButton_buttonIsShowing_removeView() {
+    public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
 
         mMagnificationModeSwitch.removeButton();
 
         verify(mWindowManager).removeView(mSpyImageView);
         verify(mViewPropertyAnimator).cancel();
+        verify(mContext).unregisterComponentCallbacks(mMagnificationModeSwitch);
     }
 
     @Test
@@ -464,6 +467,13 @@
     }
 
     @Test
+    public void showButton_registerComponentCallbacks() {
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
+    }
+
+    @Test
     public void onLocaleChanged_buttonIsShowing_updateA11yWindowTitle() {
         final String newA11yWindowTitle = "new a11y window title";
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index 216f63f..a56218b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -91,7 +91,6 @@
         verify(mModeSwitch).onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
     }
 
-
     @Test
     public void testOnSwitchClick_showWindowModeButton_invokeListener() {
         mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 5ad6517..1dd5e22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Choreographer.FrameCallback;
 import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -32,7 +34,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
@@ -42,9 +43,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.ValueAnimator;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PointF;
@@ -52,6 +55,7 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.text.TextUtils;
 import android.view.Display;
@@ -71,6 +75,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.util.leak.ReferenceTestUtils;
+import com.android.systemui.utils.os.FakeHandler;
 
 import org.junit.After;
 import org.junit.Before;
@@ -85,13 +90,12 @@
 import java.util.List;
 
 @LargeTest
+@TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner.class)
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
     @Mock
-    private Handler mHandler;
-    @Mock
     private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     @Mock
     private MirrorWindowControl mMirrorWindowControl;
@@ -99,17 +103,21 @@
     private WindowMagnifierCallback mWindowMagnifierCallback;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private Handler mHandler;
     private TestableWindowManager mWindowManager;
     private SysUiState mSysUiState = new SysUiState();
     private Resources mResources;
     private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
+    private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = Mockito.spy(getContext());
+        mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         mWindowManager = spy(new TestableWindowManager(wm));
@@ -121,17 +129,11 @@
             return null;
         }).when(mSfVsyncFrameProvider).postFrameCallback(
                 any(FrameCallback.class));
-        doAnswer(invocation -> {
-            final Runnable runnable = invocation.getArgument(0);
-            runnable.run();
-            return null;
-        }).when(mHandler).post(
-                any(Runnable.class));
         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
 
         mResources = getContext().getOrCreateTestableResources().getResources();
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext);
+                mContext, mValueAnimator);
         mWindowMagnificationController = new WindowMagnificationController(mContext,
                 mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider,
                 mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState);
@@ -144,6 +146,7 @@
     public void tearDown() {
         mInstrumentation.runOnMainSync(
                 () -> mWindowMagnificationController.deleteWindowMagnification());
+        mValueAnimator.cancel();
     }
 
     @Test
@@ -223,13 +226,9 @@
         final int screenSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.magnification_max_frame_size) * 10;
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-        //We need to initialize new one because the window size is determined when initialization.
-        final WindowMagnificationController controller = new WindowMagnificationController(mContext,
-                mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider,
-                mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState);
 
         mInstrumentation.runOnMainSync(() -> {
-            controller.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
 
@@ -242,17 +241,17 @@
     }
 
     @Test
-    public void deleteWindowMagnification_destroyControl() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
+    public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
 
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.deleteWindowMagnification());
 
         verify(mMirrorWindowControl).destroyControl();
+        verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
     }
 
     @Test
@@ -288,12 +287,6 @@
 
     @Test
     public void setScale_enabled_expectedValueAndUpdateStateDescription() {
-        doAnswer(invocation -> {
-            final Runnable runnable = invocation.getArgument(0);
-            runnable.run();
-            return null;
-        }).when(mHandler).postDelayed(any(Runnable.class), anyLong());
-
         mInstrumentation.runOnMainSync(
                 () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f,
                         Float.NaN, Float.NaN));
@@ -322,11 +315,7 @@
 
     @Test
     public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
-        final Display display = Mockito.spy(mContext.getDisplay());
-        final int currentRotation = display.getRotation();
-        final int newRotation = (currentRotation + 1) % 4;
-        when(display.getRotation()).thenReturn(newRotation);
-        when(mContext.getDisplay()).thenReturn(display);
+        final int newRotation = simulateRotateTheDevice();
         final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
         final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
         final float displayWidth = windowBounds.width();
@@ -535,6 +524,30 @@
     }
 
     @Test
+    public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
+        final Configuration config = mContext.getResources().getConfiguration();
+        config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
+                : ORIENTATION_LANDSCAPE;
+        final int newRotation = simulateRotateTheDevice();
+
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                        Float.NaN, Float.NaN));
+
+        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+    }
+
+    @Test
+    public void enableWindowMagnification_registerComponentCallback() {
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN,
+                        Float.NaN,
+                        Float.NaN));
+
+        verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
+    }
+
+    @Test
     public void onLocaleChanged_enabled_updateA11yWindowTitle() {
         final String newA11yWindowTitle = "new a11y window title";
         mInstrumentation.runOnMainSync(() -> {
@@ -610,4 +623,14 @@
                 .build();
         mWindowManager.setWindowInsets(testInsets);
     }
+
+    @Surface.Rotation
+    private int simulateRotateTheDevice() {
+        final Display display = Mockito.spy(mContext.getDisplay());
+        final int currentRotation = display.getRotation();
+        final int newRotation = (currentRotation + 1) % 4;
+        when(display.getRotation()).thenReturn(newRotation);
+        when(mContext.getDisplay()).thenReturn(display);
+        return newRotation;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 343658d..d3f30c50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -30,7 +30,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
@@ -159,15 +158,6 @@
     }
 
     @Test
-    public void onConfigurationChanged_updateModeSwitches() {
-        final Configuration config = new Configuration();
-        config.densityDpi = Configuration.DENSITY_DPI_ANY;
-        mWindowMagnification.onConfigurationChanged(config);
-
-        verify(mModeSwitchesController).onConfigurationChanged(anyInt());
-    }
-
-    @Test
     public void overviewProxyIsConnected_noController_resetFlag() {
         mOverviewProxyListener.onConnectionChanged(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 3e19cc4..cdffaec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -192,7 +192,7 @@
     public void test_holdsWakeLockWhenGoingToLowPowerDelayed() {
         // Transition to low power mode will be delayed to let
         // animations play at 60 fps.
-        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
         mHandlerFake.setMode(QUEUEING);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -209,7 +209,7 @@
     public void test_releasesWakeLock_abortingLowPowerDelayed() {
         // Transition to low power mode will be delayed to let
         // animations play at 60 fps.
-        when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+        when(mDozeParameters.shouldDelayDisplayDozeTransition()).thenReturn(true);
         mHandlerFake.setMode(QUEUEING);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
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/appwidgets/ComplicationPrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java
deleted file mode 100644
index adf110b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationPrimerTest.java
+++ /dev/null
@@ -1,191 +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.appwidgets;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.view.Gravity;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.appwidgets.dagger.AppWidgetComponent;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.Before;
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ComplicationPrimerTest extends SysuiTestCase {
-    @Rule
-    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
-
-    @Rule
-    public SysuiTestableContext mContext = new SysuiTestableContext(
-            InstrumentationRegistry.getContext(), mLeakCheck);
-
-    @Mock
-    Resources mResources;
-
-    @Mock
-    AppWidgetComponent mAppWidgetComplicationComponent1;
-    @Mock
-    AppWidgetComponent mAppWidgetComplicationComponent2;
-
-    @Mock
-    ComplicationProvider mComplicationProvider1;
-
-    @Mock
-    ComplicationProvider mComplicationProvider2;
-
-    final ComponentName mAppComplicationComponent1 =
-            ComponentName.unflattenFromString("com.foo.bar/.Baz");
-    final ComponentName mAppComplicationComponent2 =
-            ComponentName.unflattenFromString("com.foo.bar/.Baz2");
-
-    final int mAppComplicationGravity1 = Gravity.BOTTOM | Gravity.START;
-    final int mAppComplicationGravity2 = Gravity.BOTTOM | Gravity.END;
-
-    final String[] mComponents = new String[]{mAppComplicationComponent1.flattenToString(),
-            mAppComplicationComponent2.flattenToString() };
-    final int[] mPositions = new int[]{mAppComplicationGravity1, mAppComplicationGravity2};
-
-    @Mock
-    DreamOverlayStateController mDreamOverlayStateController;
-
-    @Mock
-    AppWidgetComponent.Factory mAppWidgetComplicationProviderFactory;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent1), any()))
-                .thenReturn(mAppWidgetComplicationComponent1);
-        when(mAppWidgetComplicationComponent1.getAppWidgetComplicationProvider())
-                .thenReturn(mComplicationProvider1);
-        when(mAppWidgetComplicationProviderFactory.build(eq(mAppComplicationComponent2), any()))
-                .thenReturn(mAppWidgetComplicationComponent2);
-        when(mAppWidgetComplicationComponent2.getAppWidgetComplicationProvider())
-                .thenReturn(mComplicationProvider2);
-        when(mResources.getIntArray(R.array.config_dreamComplicationPositions))
-                .thenReturn(mPositions);
-        when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications))
-                .thenReturn(mComponents);
-    }
-
-    @Test
-    public void testLoading() {
-        final ComplicationPrimer primer = new ComplicationPrimer(mContext,
-                mResources,
-                mDreamOverlayStateController,
-                mAppWidgetComplicationProviderFactory);
-
-        // Inform primer to begin.
-        primer.onBootCompleted();
-
-        // Verify the first component is added to the state controller with the proper position.
-        {
-            final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
-                    ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
-            verify(mAppWidgetComplicationProviderFactory, times(1))
-                    .build(eq(mAppComplicationComponent1),
-                    layoutParamsArgumentCaptor.capture());
-
-            assertEquals(layoutParamsArgumentCaptor.getValue().startToStart,
-                    ConstraintLayout.LayoutParams.PARENT_ID);
-            assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
-                    ConstraintLayout.LayoutParams.PARENT_ID);
-
-            verify(mDreamOverlayStateController, times(1))
-                    .addComplication(eq(mComplicationProvider1));
-        }
-
-        // Verify the second component is added to the state controller with the proper position.
-        {
-            final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
-                    ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
-            verify(mAppWidgetComplicationProviderFactory, times(1))
-                    .build(eq(mAppComplicationComponent2),
-                    layoutParamsArgumentCaptor.capture());
-
-            assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd,
-                    ConstraintLayout.LayoutParams.PARENT_ID);
-            assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
-                    ConstraintLayout.LayoutParams.PARENT_ID);
-            verify(mDreamOverlayStateController, times(1))
-                    .addComplication(eq(mComplicationProvider1));
-        }
-    }
-
-    @Test
-    public void testNoComponents() {
-        when(mResources.getStringArray(R.array.config_dreamAppWidgetComplications))
-                .thenReturn(new String[]{});
-
-        final ComplicationPrimer primer = new ComplicationPrimer(mContext,
-                mResources,
-                mDreamOverlayStateController,
-                mAppWidgetComplicationProviderFactory);
-
-        // Inform primer to begin.
-        primer.onBootCompleted();
-
-
-        // Make sure there is no request to add a widget if no components are specified by the
-        // product.
-        verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any());
-        verify(mDreamOverlayStateController, never()).addComplication(any());
-    }
-
-    @Test
-    public void testNoPositions() {
-        when(mResources.getIntArray(R.array.config_dreamComplicationPositions))
-                .thenReturn(new int[]{});
-
-        final ComplicationPrimer primer = new ComplicationPrimer(mContext,
-                mResources,
-                mDreamOverlayStateController,
-                mAppWidgetComplicationProviderFactory);
-
-        primer.onBootCompleted();
-
-        // Make sure there is no request to add a widget if no positions are specified by the
-        // product.
-        verify(mAppWidgetComplicationProviderFactory, never()).build(any(), any());
-        verify(mDreamOverlayStateController, never()).addComplication(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java
deleted file mode 100644
index f538112..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/ComplicationProviderTest.java
+++ /dev/null
@@ -1,137 +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.dreams.appwidgets;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.isNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetHostView;
-import android.content.ComponentName;
-import android.testing.AndroidTestingRunner;
-import android.widget.RemoteViews;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.dreams.ComplicationHost;
-import com.android.systemui.dreams.ComplicationHostView;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.utils.leaks.LeakCheckedTest;
-
-import org.junit.Before;
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ComplicationProviderTest extends SysuiTestCase {
-    @Mock
-    ActivityStarter mActivityStarter;
-
-    @Mock
-    ComponentName mComponentName;
-
-    @Mock
-    AppWidgetProvider mAppWidgetProvider;
-
-    @Mock
-    AppWidgetHostView mAppWidgetHostView;
-
-    @Mock
-    ComplicationHost.CreationCallback mCreationCallback;
-
-    @Mock
-    ComplicationHost.InteractionCallback mInteractionCallback;
-
-    @Mock
-    PendingIntent mPendingIntent;
-
-    @Mock
-    RemoteViews.RemoteResponse mRemoteResponse;
-
-    ComplicationProvider mComplicationProvider;
-
-    RemoteViews.InteractionHandler mInteractionHandler;
-
-    @Rule
-    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
-
-    @Rule
-    public SysuiTestableContext mContext = new SysuiTestableContext(
-            InstrumentationRegistry.getContext(), mLeakCheck);
-
-    ComplicationHostView.LayoutParams mLayoutParams = new ComplicationHostView.LayoutParams(
-            ComplicationHostView.LayoutParams.MATCH_PARENT,
-            ComplicationHostView.LayoutParams.MATCH_PARENT);
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mPendingIntent.isActivity()).thenReturn(true);
-        when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView);
-
-        mComplicationProvider = new ComplicationProvider(
-                mActivityStarter,
-                mComponentName,
-                mAppWidgetProvider,
-                mLayoutParams
-        );
-
-        final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture =
-                ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class);
-
-        mComplicationProvider.onCreateComplication(mContext, mCreationCallback,
-                mInteractionCallback);
-        verify(mAppWidgetHostView, times(1))
-                .setInteractionHandler(creationCallbackCapture.capture());
-        mInteractionHandler = creationCallbackCapture.getValue();
-    }
-
-    @Test
-    public void testWidgetBringup() {
-        // Make sure widget was requested.
-        verify(mAppWidgetProvider, times(1)).getWidget(eq(mComponentName));
-
-        // Make sure widget was returned to callback.
-        verify(mCreationCallback, times(1)).onCreated(eq(mAppWidgetHostView),
-                eq(mLayoutParams));
-    }
-
-    @Test
-    public void testWidgetInteraction() {
-        // Trigger interaction.
-        mInteractionHandler.onInteraction(mAppWidgetHostView, mPendingIntent,
-                mRemoteResponse);
-
-        // Ensure activity is started.
-        verify(mActivityStarter, times(1))
-                .startPendingIntentDismissingKeyguard(eq(mPendingIntent), isNull(),
-                        eq(mAppWidgetHostView));
-        // Verify exit is requested.
-        verify(mInteractionCallback, times(1)).onExit();
-    }
-}
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/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 3e8e874..73d2b0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
 import org.junit.After;
@@ -92,7 +93,8 @@
                         mock(DumpManager.class),
                         mock(AutoHideController.class),
                         mock(LightBarController.class),
-                        Optional.of(mock(Pip.class))));
+                        Optional.of(mock(Pip.class)),
+                        Optional.of(mock(BackAnimation.class))));
         initializeNavigationBars();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 5003013..9ca898b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -95,6 +95,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
 
@@ -105,7 +106,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 import java.util.Optional;
 
@@ -383,7 +383,8 @@
                 mAutoHideController,
                 mAutoHideControllerFactory,
                 Optional.of(mTelecomManager),
-                mInputMethodManager);
+                mInputMethodManager,
+                Optional.of(mock(BackAnimation.class)));
         return spy(factory.create(context));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 03a0da7..4a6bbbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -24,6 +24,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -73,7 +74,7 @@
     private DeviceConfigProxyFake mProxyFake;
 
     private void setUpLocal(String deviceConfigActivity, String defaultActivity,
-            boolean validateActivity, boolean enableSetting) {
+            boolean validateActivity, boolean enableSetting, boolean enableOnLockScreen) {
         MockitoAnnotations.initMocks(this);
         int enableSettingInt = enableSetting ? 1 : 0;
 
@@ -91,6 +92,8 @@
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).thenReturn(true);
         mContext.getOrCreateTestableResources().addOverride(R.string.def_qr_code_component,
                 defaultActivity);
+        mContext.getOrCreateTestableResources().addOverride(
+                android.R.bool.config_enableQrCodeScannerOnLockScreen, enableOnLockScreen);
 
         mProxyFake = new DeviceConfigProxyFake();
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -126,7 +129,8 @@
     @Test
     public void qrCodeScannerInit_withoutDefaultValue() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "", /* validateActivity */ true, /* enableSetting */true);
+                "", /* validateActivity */ true, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
         assertThat(mController.isEnabledForQuickSettings()).isFalse();
@@ -135,7 +139,8 @@
     @Test
     public void qrCodeScannerInit_withIncorrectDefaultValue() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ false, /* enableSetting */ true);
+                "abc/.def", /* validateActivity */ false, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
     }
@@ -143,7 +148,8 @@
     @Test
     public void qrCodeScannerInit_withCorrectDefaultValue() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ true, /* enableSetting */true);
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -152,7 +158,8 @@
     @Test
     public void qrCodeScannerInit_withCorrectDeviceConfig() {
         setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */
-                "", /* validateActivity */ true, /* enableSetting */true);
+                "", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -161,7 +168,8 @@
     @Test
     public void qrCodeScannerInit_withCorrectDeviceConfig_withCorrectDefaultValue() {
         setUpLocal(/* deviceConfigActivity */ "abc/.def", /* defaultActivity */
-                "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true);
+                "xyz/.qrs", /* validateActivity */true, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -170,7 +178,8 @@
     @Test
     public void qrCodeScannerInit_withCorrectDeviceConfig_fullActivity() {
         setUpLocal(/* deviceConfigActivity */ "abc/abc.def", /* defaultActivity */
-                "", /* validateActivity */  true, /* enableSetting */ true);
+                "", /* validateActivity */  true, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/abc.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -179,7 +188,8 @@
     @Test
     public void qrCodeScannerInit_withIncorrectDeviceConfig() {
         setUpLocal(/* deviceConfigActivity */ "def/.efg", /* defaultActivity */
-                "", /* validateActivity */ false, /* enableSetting */ true);
+                "", /* validateActivity */ false, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
         assertThat(mController.isEnabledForQuickSettings()).isFalse();
@@ -188,7 +198,8 @@
     @Test
     public void verifyDeviceConfigChange_withDefaultActivity() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ true, /* enableSetting */true);
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -214,7 +225,8 @@
     @Test
     public void verifyDeviceConfigChange_withoutDefaultActivity() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "", /* validateActivity */ true, /* enableSetting */ true);
+                "", /* validateActivity */ true, /* enableSetting */ true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
         assertThat(mController.isEnabledForQuickSettings()).isFalse();
@@ -239,7 +251,8 @@
     @Test
     public void verifyDeviceConfigChangeToSameValue() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "", /* validateActivity */ true, /* enableSetting */true);
+                "", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -261,7 +274,8 @@
     @Test
     public void verifyPreferenceChange() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ true, /* enableSetting */true);
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
                 UserHandle.USER_CURRENT);
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
@@ -278,7 +292,8 @@
     @Test
     public void verifyPreferenceChangeToSameValue() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ true, /* enableSetting */true);
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -301,7 +316,8 @@
     @Test
     public void verifyUnregisterRegisterChangeObservers() {
         setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
-                "abc/.def", /* validateActivity */ true, /* enableSetting */true);
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
@@ -312,7 +328,7 @@
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
         assertThat(mController.isEnabledForQuickSettings()).isFalse();
 
-        // Unregister once again and make sure, it affect affect the next register event
+        // Unregister once again and make sure it affects the next register event
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
         mController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -321,4 +337,15 @@
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isEnabledForQuickSettings()).isTrue();
     }
+
+    @Test
+    public void verifyDisableLockscreenButton() {
+        setUpLocal(/* deviceConfigActivity */ null, /* defaultActivity */
+                "abc/.def", /* validateActivity */ true, /* enableSetting */true,
+                /* enableOnLockScreen */ false);
+        assertThat(mController.getIntent()).isNotNull();
+        assertThat(mController.isEnabledForLockScreenButton()).isFalse();
+        assertThat(mController.isEnabledForQuickSettings()).isTrue();
+        assertThat(getSettingsQRCodeDefaultComponent()).isNull();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 26f04fc..354bb51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -54,8 +54,6 @@
     @Mock
     private lateinit var userInfoController: UserInfoController
     @Mock
-    private lateinit var qsPanelController: QSPanelController
-    @Mock
     private lateinit var multiUserSwitchController: MultiUserSwitchController
     @Mock
     private lateinit var globalActionsDialog: GlobalActionsDialogLite
@@ -81,7 +79,7 @@
         view = LayoutInflater.from(context)
                 .inflate(R.layout.footer_actions, null) as FooterActionsView
 
-        controller = FooterActionsController(view, qsPanelController, activityStarter,
+        controller = FooterActionsController(view, activityStarter,
                 userManager, userTracker, userInfoController, multiUserSwitchController,
                 deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 8b19c50..f43e68f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -18,7 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ClipData;
@@ -31,6 +35,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -60,13 +66,20 @@
     private TextView mBuildText;
     @Mock
     private FooterActionsController mFooterActionsController;
+    @Mock
+    private FalsingManager mFalsingManager;
+    @Mock
+    private ActivityStarter mActivityStarter;
 
     private QSFooterViewController mController;
+    private View mEditButton;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mEditButton = new View(mContext);
+
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
 
         mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
@@ -77,9 +90,11 @@
 
         when(mView.isAttachedToWindow()).thenReturn(true);
         when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
+        when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton);
 
-        mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController,
-                mQuickQSPanelController, mFooterActionsController);
+        mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager,
+                mActivityStarter, mQSPanelController, mQuickQSPanelController,
+                mFooterActionsController);
 
         mController.init();
     }
@@ -99,4 +114,27 @@
         verify(mClipboardManager).setPrimaryClip(captor.capture());
         assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
     }
+
+    @Test
+    public void testEditButton_falseTap() {
+        when(mFalsingManager.isFalseTap(anyInt())).thenReturn(true);
+
+        mEditButton.performClick();
+
+        verify(mQSPanelController, never()).showEdit(any());
+        verifyZeroInteractions(mActivityStarter);
+    }
+
+    @Test
+    public void testEditButton_realTap() {
+        when(mFalsingManager.isFalseTap(anyInt())).thenReturn(false);
+
+        mEditButton.performClick();
+
+        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+
+        verify(mActivityStarter).postQSRunnableDismissingKeyguard(captor.capture());
+        captor.getValue().run();
+        verify(mQSPanelController).showEdit(mEditButton);
+    }
 }
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/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index b832577..25dd23a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -1968,7 +1968,7 @@
         }
 
         @Override
-        public int compare(ListEntry o1, ListEntry o2) {
+        public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
             boolean contains1 = mPreferredPackages.contains(
                     o1.getRepresentativeEntry().getSbn().getPackageName());
             boolean contains2 = mPreferredPackages.contains(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
index f2e7081..bc32759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
@@ -28,6 +28,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
 import android.graphics.Color;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -151,4 +155,35 @@
         // THEN the entry is NOT in the fgs section
         assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
     }
+
+    @Test
+    public void testIncludeCallInSection_importanceDefault() {
+        // GIVEN the notification represents a call with > min importance
+        mEntryBuilder
+                .setImportance(IMPORTANCE_DEFAULT)
+                .modifyNotification(mContext)
+                .setStyle(makeCallStyle());
+
+        // THEN the entry is in the fgs section
+        assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    @Test
+    public void testDiscludeCallInSection_importanceMin() {
+        // GIVEN the notification represents a call with min importance
+        mEntryBuilder
+                .setImportance(IMPORTANCE_MIN)
+                .modifyNotification(mContext)
+                .setStyle(makeCallStyle());
+
+        // THEN the entry is NOT in the fgs section
+        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    private Notification.CallStyle makeCallStyle() {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent("action"), PendingIntent.FLAG_IMMUTABLE);
+        final Person person = new Person.Builder().setName("person").build();
+        return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index a46b440..8deac94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,18 +24,24 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
@@ -47,12 +53,15 @@
     // captured listeners and pluggables:
     private lateinit var promoter: NotifPromoter
     private lateinit var peopleSectioner: NotifSectioner
+    private lateinit var peopleComparator: NotifComparator
 
     @Mock private lateinit var pipeline: NotifPipeline
     @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
     @Mock private lateinit var channel: NotificationChannel
     @Mock private lateinit var headerController: NodeController
     private lateinit var entry: NotificationEntry
+    private lateinit var entryA: NotificationEntry
+    private lateinit var entryB: NotificationEntry
 
     private lateinit var coordinator: ConversationCoordinator
 
@@ -70,8 +79,15 @@
         }
 
         peopleSectioner = coordinator.sectioner
+        peopleComparator = coordinator.comparator
 
         entry = NotificationEntryBuilder().setChannel(channel).build()
+
+        val section = NotifSection(peopleSectioner, 0)
+        entryA = NotificationEntryBuilder().setChannel(channel)
+            .setSection(section).setTag("A").build()
+        entryB = NotificationEntryBuilder().setChannel(channel)
+            .setSection(section).setTag("B").build()
     }
 
     @Test
@@ -90,4 +106,36 @@
         assertTrue(peopleSectioner.isInSection(entry))
         assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build()))
     }
+
+    @Test
+    fun testComparatorIgnoresFromOtherSection() {
+        val e1 = NotificationEntryBuilder().setId(1).setChannel(channel).build()
+        val e2 = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+
+        // wrong section -- never classify
+        assertThat(peopleComparator.compare(e1, e2)).isEqualTo(0)
+        verify(peopleNotificationIdentifier, never()).getPeopleNotificationType(any())
+    }
+
+    @Test
+    fun testComparatorPutsImportantPeopleFirst() {
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
+            .thenReturn(TYPE_IMPORTANT_PERSON)
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
+            .thenReturn(TYPE_PERSON)
+
+        // only put people notifications in this section
+        assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1)
+    }
+
+    @Test
+    fun testComparatorEquatesPeopleWithSameType() {
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA))
+            .thenReturn(TYPE_PERSON)
+        whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB))
+            .thenReturn(TYPE_PERSON)
+
+        // only put people notifications in this section
+        assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
deleted file mode 100644
index 8ee892c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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.systemui.statusbar.notification.collection.coordinator;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.render.NodeController;
-import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-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.ArrayList;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class HeadsUpCoordinatorTest extends SysuiTestCase {
-
-    private HeadsUpCoordinator mCoordinator;
-
-    // captured listeners and pluggables:
-    private NotifCollectionListener mCollectionListener;
-    private NotifPromoter mNotifPromoter;
-    private NotifLifetimeExtender mNotifLifetimeExtender;
-    private OnHeadsUpChangedListener mOnHeadsUpChangedListener;
-    private NotifSectioner mNotifSectioner;
-
-    @Mock private NotifPipeline mNotifPipeline;
-    @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private HeadsUpViewBinder mHeadsUpViewBinder;
-    @Mock private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private NotifLifetimeExtender.OnEndLifetimeExtensionCallback mEndLifetimeExtension;
-    @Mock private NodeController mHeaderController;
-
-    private NotificationEntry mEntry;
-    private final FakeSystemClock mClock = new FakeSystemClock();
-    private final FakeExecutor mExecutor = new FakeExecutor(mClock);
-    private final ArrayList<NotificationEntry> mHuns = new ArrayList();
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mCoordinator = new HeadsUpCoordinator(
-                mHeadsUpManager,
-                mHeadsUpViewBinder,
-                mNotificationInterruptStateProvider,
-                mRemoteInputManager,
-                mHeaderController,
-                mExecutor);
-
-        mCoordinator.attach(mNotifPipeline);
-
-        // capture arguments:
-        ArgumentCaptor<NotifCollectionListener> notifCollectionCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        ArgumentCaptor<NotifPromoter> notifPromoterCaptor =
-                ArgumentCaptor.forClass(NotifPromoter.class);
-        ArgumentCaptor<NotifLifetimeExtender> notifLifetimeExtenderCaptor =
-                ArgumentCaptor.forClass(NotifLifetimeExtender.class);
-        ArgumentCaptor<OnHeadsUpChangedListener> headsUpChangedListenerCaptor =
-                ArgumentCaptor.forClass(OnHeadsUpChangedListener.class);
-
-        verify(mNotifPipeline).addCollectionListener(notifCollectionCaptor.capture());
-        verify(mNotifPipeline).addPromoter(notifPromoterCaptor.capture());
-        verify(mNotifPipeline).addNotificationLifetimeExtender(
-                notifLifetimeExtenderCaptor.capture());
-        verify(mHeadsUpManager).addListener(headsUpChangedListenerCaptor.capture());
-
-        given(mHeadsUpManager.getAllEntries()).willAnswer(i -> mHuns.stream());
-        given(mHeadsUpManager.isAlerting(anyString())).willAnswer(i -> {
-            String key = i.getArgument(0);
-            for (NotificationEntry entry : mHuns) {
-                if (entry.getKey().equals(key)) return true;
-            }
-            return false;
-        });
-        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L);
-
-        mCollectionListener = notifCollectionCaptor.getValue();
-        mNotifPromoter = notifPromoterCaptor.getValue();
-        mNotifLifetimeExtender = notifLifetimeExtenderCaptor.getValue();
-        mOnHeadsUpChangedListener = headsUpChangedListenerCaptor.getValue();
-
-        mNotifSectioner = mCoordinator.getSectioner();
-        mNotifLifetimeExtender.setCallback(mEndLifetimeExtension);
-        mEntry = new NotificationEntryBuilder().build();
-    }
-
-    @Test
-    public void testCancelStickyNotification() {
-        when(mHeadsUpManager.isSticky(anyString())).thenReturn(true);
-        addHUN(mEntry);
-        when(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true);
-        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L);
-        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0));
-        mClock.advanceTime(1000L);
-        mExecutor.runAllReady();
-        verify(mHeadsUpManager, times(0))
-                .removeNotification(anyString(), eq(false));
-        verify(mHeadsUpManager, times(1))
-                .removeNotification(anyString(), eq(true));
-    }
-
-    @Test
-    public void testCancelUpdatedStickyNotification() {
-        when(mHeadsUpManager.isSticky(anyString())).thenReturn(true);
-        addHUN(mEntry);
-        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L);
-        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0));
-        mClock.advanceTime(1000L);
-        mExecutor.runAllReady();
-        verify(mHeadsUpManager, times(0))
-                .removeNotification(anyString(), eq(false));
-        verify(mHeadsUpManager, times(0))
-                .removeNotification(anyString(), eq(true));
-    }
-
-    @Test
-    public void testCancelNotification() {
-        when(mHeadsUpManager.isSticky(anyString())).thenReturn(false);
-        addHUN(mEntry);
-        when(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L);
-        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0));
-        mClock.advanceTime(1000L);
-        mExecutor.runAllReady();
-        verify(mHeadsUpManager, times(1))
-                .removeNotification(anyString(), eq(false));
-        verify(mHeadsUpManager, times(0))
-                .removeNotification(anyString(), eq(true));
-    }
-
-    @Test
-    public void testPromotesCurrentHUN() {
-        // GIVEN the current HUN is set to mEntry
-        addHUN(mEntry);
-
-        // THEN only promote the current HUN, mEntry
-        assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry));
-        assertFalse(mNotifPromoter.shouldPromoteToTopLevel(new NotificationEntryBuilder()
-                .setPkg("test-package2")
-                .build()));
-    }
-
-    @Test
-    public void testIncludeInSectionCurrentHUN() {
-        // GIVEN the current HUN is set to mEntry
-        addHUN(mEntry);
-
-        // THEN only section the current HUN, mEntry
-        assertTrue(mNotifSectioner.isInSection(mEntry));
-        assertFalse(mNotifSectioner.isInSection(new NotificationEntryBuilder()
-                .setPkg("test-package")
-                .build()));
-    }
-
-    @Test
-    public void testLifetimeExtendsCurrentHUN() {
-        // GIVEN there is a HUN, mEntry
-        addHUN(mEntry);
-
-        given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer(i -> {
-            String key = i.getArgument(0);
-            for (NotificationEntry entry : mHuns) {
-                if (entry.getKey().equals(key)) return false;
-            }
-            return true;
-        });
-        // THEN only the current HUN, mEntry, should be lifetimeExtended
-        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0));
-        assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(
-                new NotificationEntryBuilder()
-                        .setPkg("test-package")
-                        .build(), /* cancellationReason */ 0));
-    }
-
-    @Test
-    public void testShowHUNOnInflationFinished() {
-        // WHEN a notification should HUN and its inflation is finished
-        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true);
-
-        ArgumentCaptor<BindCallback> bindCallbackCaptor =
-                ArgumentCaptor.forClass(BindCallback.class);
-        mCollectionListener.onEntryAdded(mEntry);
-        verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), bindCallbackCaptor.capture());
-
-        bindCallbackCaptor.getValue().onBindFinished(mEntry);
-
-        // THEN we tell the HeadsUpManager to show the notification
-        verify(mHeadsUpManager).showNotification(mEntry);
-    }
-
-    @Test
-    public void testNoHUNOnInflationFinished() {
-        // WHEN a notification shouldn't HUN and its inflation is finished
-        when(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false);
-        ArgumentCaptor<BindCallback> bindCallbackCaptor =
-                ArgumentCaptor.forClass(BindCallback.class);
-        mCollectionListener.onEntryAdded(mEntry);
-
-        // THEN we never bind the heads up view or tell HeadsUpManager to show the notification
-        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(
-                eq(mEntry), bindCallbackCaptor.capture());
-        verify(mHeadsUpManager, never()).showNotification(mEntry);
-    }
-
-    @Test
-    public void testOnEntryRemovedRemovesHeadsUpNotification() {
-        // GIVEN the current HUN is mEntry
-        addHUN(mEntry);
-
-        // WHEN mEntry is removed from the notification collection
-        mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0);
-        when(mRemoteInputManager.isSpinning(any())).thenReturn(false);
-
-        // THEN heads up manager should remove the entry
-        verify(mHeadsUpManager).removeNotification(mEntry.getKey(), false);
-    }
-
-    private void addHUN(NotificationEntry entry) {
-        mHuns.add(entry);
-        when(mHeadsUpManager.getTopEntry()).thenReturn(entry);
-        mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, entry != null);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
new file mode 100644
index 0000000..c67a233
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -0,0 +1,238 @@
+/*
+ * 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.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.BDDMockito.given
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.ArrayList
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class HeadsUpCoordinatorTest : SysuiTestCase() {
+    private lateinit var mCoordinator: HeadsUpCoordinator
+
+    // captured listeners and pluggables:
+    private lateinit var mCollectionListener: NotifCollectionListener
+    private lateinit var mNotifPromoter: NotifPromoter
+    private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender
+    private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
+    private lateinit var mNotifSectioner: NotifSectioner
+
+    private val mNotifPipeline: NotifPipeline = mock()
+    private val mHeadsUpManager: HeadsUpManager = mock()
+    private val mHeadsUpViewBinder: HeadsUpViewBinder = mock()
+    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
+    private val mRemoteInputManager: NotificationRemoteInputManager = mock()
+    private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
+    private val mHeaderController: NodeController = mock()
+
+    private lateinit var mEntry: NotificationEntry
+    private val mExecutor = FakeExecutor(FakeSystemClock())
+    private val mHuns: ArrayList<NotificationEntry> = ArrayList()
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mCoordinator = HeadsUpCoordinator(
+            mHeadsUpManager,
+            mHeadsUpViewBinder,
+            mNotificationInterruptStateProvider,
+            mRemoteInputManager,
+            mHeaderController,
+            mExecutor)
+        mCoordinator.attach(mNotifPipeline)
+
+        // capture arguments:
+        mCollectionListener = withArgCaptor {
+            verify(mNotifPipeline).addCollectionListener(capture())
+        }
+        mNotifPromoter = withArgCaptor {
+            verify(mNotifPipeline).addPromoter(capture())
+        }
+        mNotifLifetimeExtender = withArgCaptor {
+            verify(mNotifPipeline).addNotificationLifetimeExtender(capture())
+        }
+        mOnHeadsUpChangedListener = withArgCaptor {
+            verify(mHeadsUpManager).addListener(capture())
+        }
+        given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() }
+        given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+            val key = invocation.getArgument<String>(0)
+            mHuns.any { entry -> entry.key == key }
+        }
+        given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation ->
+            val key = invocation.getArgument<String>(0)
+            !mHuns.any { entry -> entry.key == key }
+        }
+        whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+        mNotifSectioner = mCoordinator.sectioner
+        mNotifLifetimeExtender.setCallback(mEndLifetimeExtension)
+        mEntry = NotificationEntryBuilder().build()
+    }
+
+    @Test
+    fun testCancelStickyNotification() {
+        whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
+        addHUN(mEntry)
+        whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true)
+        whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L)
+        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+        mExecutor.advanceClockToLast()
+        mExecutor.runAllReady()
+        verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
+        verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true))
+    }
+
+    @Test
+    fun testCancelUpdatedStickyNotification() {
+        whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
+        addHUN(mEntry)
+        whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
+        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+        mExecutor.advanceClockToLast()
+        mExecutor.runAllReady()
+        verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false))
+        verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+    }
+
+    @Test
+    fun testCancelNotification() {
+        whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false)
+        addHUN(mEntry)
+        whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L)
+        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+        mExecutor.advanceClockToLast()
+        mExecutor.runAllReady()
+        verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false))
+        verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true))
+    }
+
+    @Test
+    fun testPromotesCurrentHUN() {
+        // GIVEN the current HUN is set to mEntry
+        addHUN(mEntry)
+
+        // THEN only promote the current HUN, mEntry
+        assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry))
+        assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder()
+            .setPkg("test-package2")
+            .build()))
+    }
+
+    @Test
+    fun testIncludeInSectionCurrentHUN() {
+        // GIVEN the current HUN is set to mEntry
+        addHUN(mEntry)
+
+        // THEN only section the current HUN, mEntry
+        assertTrue(mNotifSectioner.isInSection(mEntry))
+        assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder()
+            .setPkg("test-package")
+            .build()))
+    }
+
+    @Test
+    fun testLifetimeExtendsCurrentHUN() {
+        // GIVEN there is a HUN, mEntry
+        addHUN(mEntry)
+
+        // THEN only the current HUN, mEntry, should be lifetimeExtended
+        assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0))
+        assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(
+            NotificationEntryBuilder()
+                .setPkg("test-package")
+                .build(), /* cancellationReason */ 0))
+    }
+
+    @Test
+    fun testShowHUNOnInflationFinished() {
+        // WHEN a notification should HUN and its inflation is finished
+        whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
+
+        mCollectionListener.onEntryAdded(mEntry)
+        withArgCaptor<BindCallback> {
+            verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture())
+        }.onBindFinished(mEntry)
+
+        // THEN we tell the HeadsUpManager to show the notification
+        verify(mHeadsUpManager).showNotification(mEntry)
+    }
+
+    @Test
+    fun testNoHUNOnInflationFinished() {
+        // WHEN a notification shouldn't HUN and its inflation is finished
+        whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // THEN we never bind the heads up view or tell HeadsUpManager to show the notification
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any())
+        verify(mHeadsUpManager, never()).showNotification(mEntry)
+    }
+
+    @Test
+    fun testOnEntryRemovedRemovesHeadsUpNotification() {
+        // GIVEN the current HUN is mEntry
+        addHUN(mEntry)
+
+        // WHEN mEntry is removed from the notification collection
+        mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0)
+        whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false)
+
+        // THEN heads up manager should remove the entry
+        verify(mHeadsUpManager).removeNotification(mEntry.key, false)
+    }
+
+    private fun addHUN(entry: NotificationEntry) {
+        mHuns.add(entry)
+        whenever(mHeadsUpManager.topEntry).thenReturn(entry)
+        mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 917c049..d094749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -41,6 +41,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -70,6 +71,7 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private HighPriorityProvider mHighPriorityProvider;
+    @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
     @Mock private NotifPipeline mNotifPipeline;
 
     private NotificationEntry mEntry;
@@ -81,7 +83,7 @@
         KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
-                mKeyguardUpdateMonitor, mHighPriorityProvider);
+                mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider);
 
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index bde6734..3b034f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import static java.util.Objects.requireNonNull;
@@ -40,7 +41,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.SectionClassifier;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
@@ -93,7 +94,7 @@
     @Mock private NotifSection mNotifSection;
     @Mock private NotifPipeline mNotifPipeline;
     @Mock private IStatusBarService mService;
-    @Mock private ConversationNotificationManager mConvoManager;
+    @Mock private BindEventManagerImpl mBindEventManagerImpl;
     @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
     private final SectionClassifier mSectionClassifier = new SectionClassifier();
     private final NotifUiAdjustmentProvider mAdjustmentProvider =
@@ -121,7 +122,7 @@
                 mock(NotifViewBarn.class),
                 mAdjustmentProvider,
                 mService,
-                mConvoManager,
+                mBindEventManagerImpl,
                 TEST_CHILD_BIND_CUTOFF,
                 TEST_MAX_GROUP_DELAY);
 
@@ -411,7 +412,8 @@
     public void testCallConversationManagerBindWhenInflated() {
         mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry));
         mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null);
-        verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry));
+        verify(mBindEventManagerImpl, times(1)).notifyViewBound(eq(mEntry));
+        verifyNoMoreInteractions(mBindEventManagerImpl);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index f773810..4e309d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -43,6 +44,7 @@
 
     private val mediaContainerController: MediaContainerController = mock()
     private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock()
+    private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val viewBarn: NotifViewBarn = mock()
 
     private var rootController: NodeController = buildFakeController("rootController")
@@ -72,11 +74,13 @@
             fakeViewBarn.getViewByEntry(it.getArgument(0))
         }
 
-        specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager, viewBarn)
+        specBuilder = NodeSpecBuilder(mediaContainerController, sectionsFeatureManager,
+                sectionHeaderVisibilityProvider, viewBarn)
     }
 
     @Test
     fun testMultipleSectionsWithSameController() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 listOf(
                         notif(0, section0),
@@ -95,6 +99,7 @@
 
     @Test(expected = RuntimeException::class)
     fun testMultipleSectionsWithSameControllerNonConsecutive() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 listOf(
                         notif(0, section0),
@@ -108,6 +113,7 @@
 
     @Test
     fun testSimpleMapping() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
             // GIVEN a simple flat list of notifications all in the same headerless section
             listOf(
@@ -129,6 +135,7 @@
 
     @Test
     fun testSimpleMappingWithMedia() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         // WHEN media controls are enabled
         whenever(sectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true)
 
@@ -154,6 +161,8 @@
 
     @Test
     fun testHeaderInjection() {
+        // WHEN section headers are supposed to be visible
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 // GIVEN a flat list of notifications, spread across three sections
                 listOf(
@@ -177,7 +186,31 @@
     }
 
     @Test
+    fun testHeaderSuppression() {
+        // WHEN section headers are supposed to be hidden
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(false)
+        checkOutput(
+                // GIVEN a flat list of notifications, spread across three sections
+                listOf(
+                        notif(0, section0),
+                        notif(1, section0),
+                        notif(2, section1),
+                        notif(3, section2)
+                ),
+
+                // THEN each section has its header injected
+                tree(
+                        notifNode(0),
+                        notifNode(1),
+                        notifNode(2),
+                        notifNode(3)
+                )
+        )
+    }
+
+    @Test
     fun testGroups() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 // GIVEN a mixed list of top-level notifications and groups
                 listOf(
@@ -218,6 +251,7 @@
 
     @Test
     fun testSecondSectionWithNoHeader() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 // GIVEN a middle section with no associated header view
                 listOf(
@@ -247,6 +281,7 @@
 
     @Test(expected = RuntimeException::class)
     fun testRepeatedSectionsThrow() {
+        whenever(sectionHeaderVisibilityProvider.sectionHeadersVisible).thenReturn(true)
         checkOutput(
                 // GIVEN a malformed list where sections are not contiguous
                 listOf(
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/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index f21fca2..10f4435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -647,8 +647,15 @@
         mScrimController.setRawPanelExpansionFraction(0.3f);
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, SEMI_TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, SEMI_TRANSPARENT));
+
+        // Then, notification scrim should fade in
+        mScrimController.setRawPanelExpansionFraction(0.7f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, SEMI_TRANSPARENT,
+                mScrimBehind, OPAQUE));
     }
 
 
@@ -1132,6 +1139,7 @@
 
     @Test
     public void testScrimsVisible_whenShadeVisible() {
+        mScrimController.setClipsQsScrim(true);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.setRawPanelExpansionFraction(0.3f);
         // notifications scrim alpha change require calling setQsPosition
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/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index a8522c7..6364d2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -52,13 +52,14 @@
 @SmallTest
 public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
 
-    private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"};
+    private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
 
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
-    @Mock DeviceStateManager mDeviceStateManager;
-    RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
-    DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
+    @Mock
+    private DeviceStateManager mDeviceStateManager;
+    private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
+    private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
     private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
     private DeviceStateRotationLockSettingsManager mSettingsManager;
     private TestableContentResolver mContentResolver;
@@ -93,7 +94,7 @@
                                 mContentResolver,
                                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                                 UserHandle.USER_CURRENT))
-                .isEqualTo("0:0:1:2");
+                .isEqualTo("0:1:1:2:2:0");
     }
 
     @Test
@@ -125,6 +126,31 @@
     }
 
     @Test
+    public void whenDeviceStateSwitched_settingIsIgnored_loadsDefaultFallbackSetting() {
+        initializeSettingsWith();
+        mFakeRotationPolicy.setRotationLock(true);
+
+        // State 2 -> Ignored -> Fall back to state 1 which is unlocked
+        mDeviceStateCallback.onStateChanged(2);
+
+        assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
+    }
+
+    @Test
+    public void whenDeviceStateSwitched_ignoredSetting_fallbackValueChanges_usesFallbackValue() {
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+                1, DEVICE_STATE_ROTATION_LOCK_LOCKED,
+                2, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        mFakeRotationPolicy.setRotationLock(false);
+
+        // State 2 -> Ignored -> Fall back to state 1 which is locked
+        mDeviceStateCallback.onStateChanged(2);
+
+        assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+    }
+
+    @Test
     public void whenUserChangesSetting_saveSettingForCurrentState() {
         initializeSettingsWith(
                 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
@@ -159,15 +185,15 @@
     }
 
     @Test
-    public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() {
+    public void whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState() {
         initializeSettingsWith(
-                0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+                8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
         mFakeRotationPolicy.setRotationLock(true);
 
         mDeviceStateCallback.onStateChanged(1);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
-        mDeviceStateCallback.onStateChanged(0);
+        mDeviceStateCallback.onStateChanged(8);
         assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
 
         mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
@@ -178,7 +204,7 @@
                                 mContentResolver,
                                 Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                                 UserHandle.USER_CURRENT))
-                .isEqualTo("0:0:1:1");
+                .isEqualTo("1:1:8:0");
     }
 
     @Test
@@ -198,12 +224,78 @@
         assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
     }
 
+    @Test
+    public void onRotationLockStateChanged_newSettingIsPersisted() {
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
+                1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+        mDeviceStateCallback.onStateChanged(0);
+
+        mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+                /* rotationLocked= */ false,
+                /* affordanceVisible= */ true
+        );
+
+        assertThat(
+                Settings.Secure.getStringForUser(
+                        mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        UserHandle.USER_CURRENT))
+                .isEqualTo("0:2:1:2");
+    }
+
+    @Test
+    public void onRotationLockStateChanged_deviceStateIsIgnored_newSettingIsPersistedToFallback() {
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
+                1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+                2, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        mDeviceStateCallback.onStateChanged(2);
+
+        mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+                /* rotationLocked= */ true,
+                /* affordanceVisible= */ true
+        );
+
+        assertThat(
+                Settings.Secure.getStringForUser(
+                        mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        UserHandle.USER_CURRENT))
+                .isEqualTo("0:1:1:1:2:0");
+    }
+
+    @Test
+    public void onRotationLockStateChange_stateIgnored_noFallback_settingIsPersistedToPrevious() {
+        initializeSettingsWith(
+                0, DEVICE_STATE_ROTATION_LOCK_LOCKED,
+                1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
+                8, DEVICE_STATE_ROTATION_LOCK_IGNORED);
+        mDeviceStateCallback.onStateChanged(1);
+        mDeviceStateCallback.onStateChanged(8);
+
+        mDeviceStateRotationLockSettingController.onRotationLockStateChanged(
+                /* rotationLocked= */ true,
+                /* affordanceVisible= */ true
+        );
+
+        assertThat(
+                Settings.Secure.getStringForUser(
+                        mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        UserHandle.USER_CURRENT))
+                .isEqualTo("0:1:1:1:8:0");
+    }
+
     private void initializeSettingsWith(int... values) {
         if (values.length % 2 != 0) {
             throw new IllegalArgumentException("Expecting key-value pairs");
         }
         StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < values.length; sb.append(":")) {
+        for (int i = 0; i < values.length; ) {
+            if (i > 0) {
+                sb.append(":");
+            }
             sb.append(values[i++]).append(":").append(values[i++]);
         }
 
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/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 087f2e6..2126dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -22,6 +22,7 @@
 
 import android.app.AppOpsManager;
 import android.content.Intent;
+import android.content.pm.UserInfo;
 import android.location.LocationManager;
 import android.os.Handler;
 import android.os.UserHandle;
@@ -68,6 +69,8 @@
         MockitoAnnotations.initMocks(this);
         when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+        when(mUserTracker.getUserProfiles())
+                .thenReturn(ImmutableList.of(new UserInfo(0, "name", 0)));
         mDeviceConfigProxy = new DeviceConfigProxyFake();
 
         mTestableLooper = TestableLooper.get(this);
@@ -78,7 +81,8 @@
                 new Handler(mTestableLooper.getLooper()),
                 mock(BroadcastDispatcher.class),
                 mock(BootCompleteCache.class),
-                mUserTracker);
+                mUserTracker,
+                mContext.getPackageManager());
 
         mTestableLooper.processAllMessages();
     }
@@ -161,17 +165,7 @@
     @Test
     public void testCallbackNotified_additionalOps() {
         LocationChangeCallback callback = mock(LocationChangeCallback.class);
-
         mLocationController.addCallback(callback);
-
-        mTestableLooper.processAllMessages();
-
-        mLocationController.onReceive(mContext, new Intent(LocationManager.MODE_CHANGED_ACTION));
-
-        mTestableLooper.processAllMessages();
-
-        verify(callback, times(2)).onLocationSettingsChanged(anyBoolean());
-
         mDeviceConfigProxy.setProperty(
                 DeviceConfig.NAMESPACE_PRIVACY,
                 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
@@ -181,10 +175,11 @@
 
         when(mAppOpsController.getActiveAppOps())
                 .thenReturn(ImmutableList.of(
-                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "",
+                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
+                                "com.google.android.googlequicksearchbox",
                                 System.currentTimeMillis())));
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "", true);
+                "com.google.android.googlequicksearchbox", true);
 
         mTestableLooper.processAllMessages();
 
@@ -192,7 +187,67 @@
 
         when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
         mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
-                "", false);
+                "com.google.android.googlequicksearchbox", false);
+        mTestableLooper.processAllMessages();
+
+        verify(callback, times(1)).onLocationActiveChanged(false);
+    }
+
+    @Test
+    public void testCallbackNotified_additionalOps_shouldShowSystem() {
+        LocationChangeCallback callback = mock(LocationChangeCallback.class);
+        mLocationController.addCallback(callback);
+        mDeviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
+                "true",
+                true);
+        mTestableLooper.processAllMessages();
+
+        when(mAppOpsController.getActiveAppOps())
+                .thenReturn(ImmutableList.of(
+                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0,
+                                "com.google.android.gms",
+                                System.currentTimeMillis())));
+        mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+                "com.google.android.gms", true);
+
+        mTestableLooper.processAllMessages();
+
+        verify(callback, times(0)).onLocationActiveChanged(true);
+    }
+
+
+    @Test
+    public void testCallbackNotified_additionalOps_shouldNotShowSystem() {
+        LocationChangeCallback callback = mock(LocationChangeCallback.class);
+        mLocationController.addCallback(callback);
+        mDeviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED,
+                "true",
+                true);
+        mDeviceConfigProxy.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM,
+                "true",
+                true);
+        mTestableLooper.processAllMessages();
+
+        when(mAppOpsController.getActiveAppOps())
+                .thenReturn(ImmutableList.of(
+                        new AppOpItem(AppOpsManager.OP_FINE_LOCATION, 0, "com.google.android.gms",
+                                System.currentTimeMillis())));
+        mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+                "com.google.android.gms", true);
+
+        mTestableLooper.processAllMessages();
+
+        verify(callback, times(1)).onLocationActiveChanged(true);
+
+        when(mAppOpsController.getActiveAppOps()).thenReturn(ImmutableList.of());
+        mLocationController.onActiveStateChanged(AppOpsManager.OP_FINE_LOCATION, 0,
+                "com.google.android.gms", false);
         mTestableLooper.processAllMessages();
 
         verify(callback, times(1)).onLocationActiveChanged(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index f600b12..4e2736c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -68,7 +68,7 @@
             context.mainExecutor,
             context,
             screenLifecycle
-        )
+        ).apply { init() }
         deviceStates = FoldableTestUtils.findDeviceStates(context)
 
         verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
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/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 93fc0e72..2b7b977 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -254,9 +254,14 @@
     private AssociationInfo createAssociationAndNotifyApplication(
             @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
             @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) {
-        final AssociationInfo association = mService.createAssociation(userId, packageName,
-                macAddress, request.getDisplayName(), request.getDeviceProfile(),
-                request.isSelfManaged());
+        final AssociationInfo association;
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            association = mService.createAssociation(userId, packageName, macAddress,
+                    request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged());
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
 
         try {
             callback.onAssociationCreated(association);
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
index 58fc8f7..01905bb 100644
--- a/services/companion/java/com/android/server/companion/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/AssociationStore.java
@@ -49,7 +49,25 @@
     /**  Listener for any changes to {@link AssociationInfo}-s. */
     interface OnChangeListener {
         default void onAssociationChanged(
-                @ChangeType int changeType, AssociationInfo association) {}
+                @ChangeType int changeType, AssociationInfo association) {
+            switch (changeType) {
+                case CHANGE_TYPE_ADDED:
+                    onAssociationAdded(association);
+                    break;
+
+                case CHANGE_TYPE_REMOVED:
+                    onAssociationRemoved(association);
+                    break;
+
+                case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+                    onAssociationUpdated(association, true);
+                    break;
+
+                case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+                    onAssociationUpdated(association, false);
+                    break;
+            }
+        }
 
         default void onAssociationAdded(AssociationInfo association) {}
 
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index dbcdd0f..cda554e 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -125,8 +125,7 @@
             // Update the MacAddress-to-List<Association> map if needed.
             final MacAddress updatedAddress = updated.getDeviceMacAddress();
             final MacAddress currentAddress = current.getDeviceMacAddress();
-            macAddressChanged = Objects.equals(
-                    current.getDeviceMacAddress(), updated.getDeviceMacAddress());
+            macAddressChanged = Objects.equals(currentAddress, updatedAddress);
             if (macAddressChanged) {
                 if (currentAddress != null) {
                     mAddressMap.get(currentAddress).remove(id);
@@ -213,11 +212,9 @@
             final Set<Integer> ids = mAddressMap.get(address);
             if (ids == null) return Collections.emptyList();
 
-            final List<AssociationInfo> associations = new ArrayList<>();
-            for (AssociationInfo association : mIdMap.values()) {
-                if (address.equals(association.getDeviceMacAddress())) {
-                    associations.add(association);
-                }
+            final List<AssociationInfo> associations = new ArrayList<>(ids.size());
+            for (Integer id : ids) {
+                associations.add(mIdMap.get(id));
             }
 
             return Collections.unmodifiableList(associations);
@@ -263,24 +260,6 @@
         synchronized (mListeners) {
             for (OnChangeListener listener : mListeners) {
                 listener.onAssociationChanged(changeType, association);
-
-                switch (changeType) {
-                    case CHANGE_TYPE_ADDED:
-                        listener.onAssociationAdded(association);
-                        break;
-
-                    case CHANGE_TYPE_REMOVED:
-                        listener.onAssociationRemoved(association);
-                        break;
-
-                    case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
-                        listener.onAssociationUpdated(association, true);
-                        break;
-
-                    case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
-                        listener.onAssociationUpdated(association, false);
-                        break;
-                }
             }
         }
     }
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
new file mode 100644
index 0000000..be1bc79
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -0,0 +1,317 @@
+/*
+ * 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.companion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.PerUser;
+import com.android.internal.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages communication with companion applications via
+ * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
+ * the services, maintaining the connection (the binding), and invoking callback methods such as
+ * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)} and
+ * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} in the application process.
+ *
+ * <p>
+ * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
+ * utilized by {@link CompanionDeviceManagerService}):
+ * <ul>
+ * <li> {@link #bindCompanionApplication(int, String)}
+ * <li> {@link #unbindCompanionApplication(int, String)}
+ * <li> {@link #notifyCompanionApplicationDeviceAppeared(AssociationInfo)}
+ * <li> {@link #notifyCompanionApplicationDeviceDisappeared(AssociationInfo)}
+ * <li> {@link #isCompanionApplicationBound(int, String)}
+ * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
+ * </ul>
+ *
+ * @see CompanionDeviceService
+ * @see android.companion.ICompanionDeviceService
+ * @see CompanionDeviceServiceConnector
+ */
+@SuppressLint("LongLogTag")
+class CompanionApplicationController {
+    static final boolean DEBUG = false;
+    private static final String TAG = "CompanionDevice_ApplicationController";
+
+    private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
+
+    interface Callback {
+        /**
+         * @return {@code true} if should schedule rebinding.
+         *         {@code false} if we do not need to rebind.
+         */
+        boolean onCompanionApplicationBindingDied(
+                @UserIdInt int userId, @NonNull String packageName);
+
+        /**
+         * Callback after timeout for previously scheduled rebind has passed.
+         */
+        void onRebindCompanionApplicationTimeout(
+                @UserIdInt int userId, @NonNull String packageName);
+    }
+
+    private final @NonNull Context mContext;
+    private final @NonNull Callback mCallback;
+    private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
+    @GuardedBy("mBoundCompanionApplications")
+    private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
+            mBoundCompanionApplications;
+    private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
+
+    CompanionApplicationController(Context context, Callback callback) {
+        mContext = context;
+        mCallback = callback;
+        mCompanionServicesRegister = new CompanionServicesRegister();
+        mBoundCompanionApplications = new AndroidPackageMap<>();
+        mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
+    }
+
+    void onPackagesChanged(@UserIdInt int userId) {
+        mCompanionServicesRegister.invalidate(userId);
+    }
+
+    void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Log.i(TAG, "bind() u" + userId + "/" + packageName);
+
+        final List<ComponentName> companionServices =
+                mCompanionServicesRegister.forPackage(userId, packageName);
+        final List<CompanionDeviceServiceConnector> serviceConnectors;
+
+        synchronized (mBoundCompanionApplications) {
+            if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
+                if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
+                return;
+            }
+
+            serviceConnectors = CollectionUtils.map(companionServices, componentName ->
+                            new CompanionDeviceServiceConnector(mContext, userId, componentName));
+            mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
+        }
+
+        // The first connector in the list is always the primary connector: set a listener to it.
+        serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied);
+
+        // Now "bind" all the connectors: the primary one and the rest of them.
+        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+            serviceConnector.connect();
+        }
+    }
+
+    void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
+
+        final List<CompanionDeviceServiceConnector> serviceConnectors;
+        synchronized (mBoundCompanionApplications) {
+            serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
+        }
+        if (serviceConnectors == null) {
+            if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound");
+            return;
+        }
+
+        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+            serviceConnector.postUnbind();
+        }
+    }
+
+    boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (mBoundCompanionApplications) {
+            return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
+        }
+    }
+
+    private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName) {
+        mScheduledForRebindingCompanionApplications.setValueForPackage(userId, packageName, true);
+
+        Handler.getMain().postDelayed(() ->
+                onRebindingCompanionApplicationTimeout(userId, packageName), REBIND_TIMEOUT);
+    }
+
+    boolean isRebindingCompanionApplicationScheduled(
+            @UserIdInt int userId, @NonNull String packageName) {
+        return mScheduledForRebindingCompanionApplications
+                .containsValueForPackage(userId, packageName);
+    }
+
+    private void onRebindingCompanionApplicationTimeout(
+            @UserIdInt int userId, @NonNull String packageName) {
+        mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
+
+        mCallback.onRebindCompanionApplicationTimeout(userId, packageName);
+    }
+
+    void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+        if (DEBUG) {
+            Log.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
+                    + "/" + packageName);
+        }
+
+        final CompanionDeviceServiceConnector primaryServiceConnector =
+                getPrimaryServiceConnector(userId, packageName);
+        if (primaryServiceConnector == null) {
+            if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound.");
+            return;
+        }
+
+        primaryServiceConnector.postOnDeviceAppeared(association);
+    }
+
+    void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
+        final int userId = association.getUserId();
+        final String packageName = association.getPackageName();
+        if (DEBUG) {
+            Log.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
+                    + "/" + packageName);
+        }
+
+        final CompanionDeviceServiceConnector primaryServiceConnector =
+                getPrimaryServiceConnector(userId, packageName);
+        if (primaryServiceConnector == null) {
+            if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is NOT bound.");
+            return;
+        }
+
+        primaryServiceConnector.postOnDeviceDisappeared(association);
+    }
+
+    private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName);
+
+        // First: mark as NOT bound.
+        synchronized (mBoundCompanionApplications) {
+            mBoundCompanionApplications.removePackage(userId, packageName);
+        }
+
+        // Second: invoke callback, schedule rebinding if needed.
+        final boolean shouldScheduleRebind =
+                mCallback.onCompanionApplicationBindingDied(userId, packageName);
+        if (shouldScheduleRebind) {
+            scheduleRebinding(userId, packageName);
+        }
+    }
+
+    private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
+            @UserIdInt int userId, @NonNull String packageName) {
+        final List<CompanionDeviceServiceConnector> connectors;
+        synchronized (mBoundCompanionApplications) {
+            connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
+        }
+        return connectors != null ? connectors.get(0) : null;
+    }
+
+    private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
+        @Override
+        public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+                @UserIdInt int userId) {
+            return super.forUser(userId);
+        }
+
+        synchronized @NonNull List<ComponentName> forPackage(
+                @UserIdInt int userId, @NonNull String packageName) {
+            return forUser(userId).getOrDefault(packageName, Collections.emptyList());
+        }
+
+        synchronized @NonNull ComponentName primaryForPackage(
+                @UserIdInt int userId, @NonNull String packageName) {
+            // The primary service is always at the head of the list.
+            return forPackage(userId, packageName).get(0);
+        }
+
+        synchronized void invalidate(@UserIdInt int userId) {
+            remove(userId);
+        }
+
+        @Override
+        protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+            return PackageUtils.getCompanionServicesForUser(mContext, userId);
+        }
+    }
+
+    /**
+     * Associates an Android package (defined by userId + packageName) with a value of type T.
+     */
+    private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
+
+        void setValueForPackage(
+                @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
+            Map<String, T> forUser = get(userId);
+            if (forUser == null) {
+                forUser = /* Map<String, T> */ new HashMap();
+                put(userId, forUser);
+            }
+
+            forUser.put(packageName, value);
+        }
+
+        boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+            final Map<String, ?> forUser = get(userId);
+            return forUser != null && forUser.containsKey(packageName);
+        }
+
+        T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+            final Map<String, T> forUser = get(userId);
+            return forUser != null ? forUser.get(packageName) : null;
+        }
+
+        T removePackage(@UserIdInt int userId, @NonNull String packageName) {
+            final Map<String, T> forUser = get(userId);
+            if (forUser == null) return null;
+            return forUser.remove(packageName);
+        }
+
+        void dump() {
+            if (size() == 0) {
+                Log.d(TAG, "<empty>");
+                return;
+            }
+
+            for (int i = 0; i < size(); i++) {
+                final int userId = keyAt(i);
+                final Map<String, T> forUser = get(userId);
+                if (forUser.isEmpty()) {
+                    Log.d(TAG, "u" + userId + ": <empty>");
+                }
+
+                for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+                    final String packageName = packageValue.getKey();
+                    final T value = packageValue.getValue();
+                    Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
+                }
+            }
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 1e50c80..cfd3798 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -77,11 +77,13 @@
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
 import android.net.MacAddress;
 import android.net.NetworkPolicyManager;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.PowerWhitelistManager;
 import android.os.RemoteCallbackList;
@@ -185,6 +187,7 @@
     private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this);
 
     final Handler mMainHandler = Handler.getMain();
+    private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler();
     private CompanionDevicePresenceController mCompanionDevicePresenceController;
 
     /**
@@ -366,9 +369,8 @@
 
         final List<AssociationInfo> updatedAssociations =
                 mAssociationStore.getAssociationsForUser(userId);
-        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
-        BackgroundThread.getHandler().post(() ->
-                mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser));
+
+        mUserPersistenceHandler.postPersistUserState(userId);
 
         // Notify listeners if ADDED, REMOVED or UPDATED_ADDRESS_CHANGED.
         // Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in association's
@@ -381,6 +383,13 @@
         restartBleScan();
     }
 
+    private void persistStateForUser(@UserIdInt int userId) {
+        final List<AssociationInfo> updatedAssociations =
+                mAssociationStore.getAssociationsForUser(userId);
+        final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
+        mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
+    }
+
     private void notifyListeners(
             @UserIdInt int userId, @NonNull List<AssociationInfo> associations) {
         mListeners.broadcast((listener, callbackUserId) -> {
@@ -778,6 +787,12 @@
         Slog.i(LOG_TAG, "New CDM association created=" + association);
         mAssociationStore.addAssociation(association);
 
+        // If the "Device Profile" is specified, make the companion application a holder of the
+        // corresponding role.
+        if (deviceProfile != null) {
+            addRoleHolderForAssociation(getContext(), association);
+        }
+
         updateSpecialAccessPermissionForAssociatedPackage(association);
 
         return association;
@@ -921,14 +936,8 @@
 
         exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
 
-        if (!association.isSelfManaged()) {
-            if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) {
-                addRoleHolderForAssociation(getContext(), association);
-            }
-
-            if (association.isNotifyOnDeviceNearby()) {
-                restartBleScan();
-            }
+        if (association.isNotifyOnDeviceNearby()) {
+            restartBleScan();
         }
     }
 
@@ -974,19 +983,7 @@
 
     void onDeviceConnected(String address) {
         Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")");
-
         mCurrentlyConnectedDevices.add(address);
-
-        for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) {
-            if (association.getDeviceProfile() != null) {
-                Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
-                        + " to " + association.getPackageName()
-                        + " due to device connected: " + association.getDeviceMacAddress());
-
-                addRoleHolderForAssociation(getContext(), association);
-            }
-        }
-
         onDeviceNearby(address);
     }
 
@@ -1349,4 +1346,47 @@
             mService.associationCleanUp(profile);
         }
     }
+
+    /**
+     * This method must only be called from {@link CompanionDeviceShellCommand} for testing
+     * purposes only!
+     */
+    void persistState() {
+        mUserPersistenceHandler.clearMessages();
+        for (UserInfo user : mUserManager.getAliveUsers()) {
+            persistStateForUser(user.id);
+        }
+    }
+
+    /**
+     * This class is dedicated to handling requests to persist user state.
+     */
+    @SuppressLint("HandlerLeak")
+    private class PersistUserStateHandler extends Handler {
+        PersistUserStateHandler() {
+            super(BackgroundThread.get().getLooper());
+        }
+
+        /**
+         * Persists user state unless there is already an outstanding request for the given user.
+         */
+        synchronized void postPersistUserState(@UserIdInt int userId) {
+            if (!hasMessages(userId)) {
+                sendMessage(obtainMessage(userId));
+            }
+        }
+
+        /**
+         * Clears *ALL* outstanding persist requests for *ALL* users.
+         */
+        synchronized void clearMessages() {
+            removeCallbacksAndMessages(null);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            final int userId = msg.what;
+            persistStateForUser(userId);
+        }
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 5f46d5c..f2e66077 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -73,19 +73,12 @@
                 }
                 break;
 
-                case "simulate_connect": {
-                    mService.onDeviceConnected(getNextArgRequired());
-                }
-                break;
-
-                case "simulate_disconnect": {
-                    mService.onDeviceDisconnected(getNextArgRequired());
-                }
-                break;
                 case "clear-association-memory-cache": {
+                    mService.persistState();
                     mService.loadAssociationsFromDisk();
                 }
                 break;
+
                 default:
                     return handleDefaultCommands(cmd);
             }
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
new file mode 100644
index 0000000..6055a81
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -0,0 +1,85 @@
+/*
+ * 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.companion;
+
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.util.FunctionalUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+final class DataStoreUtils {
+
+    private static final String LOG_TAG = DataStoreUtils.class.getSimpleName();
+
+    static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+            throws XmlPullParserException {
+        return parser.getEventType() == START_TAG && tag.equals(parser.getName());
+    }
+
+    static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
+            throws XmlPullParserException {
+        return parser.getEventType() == END_TAG && tag.equals(parser.getName());
+    }
+
+    /**
+     * Creates {@link AtomicFile} object that represents the back-up for the given user.
+     *
+     * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
+     * possible to synchronize reads and writes to the file using the returned object.
+     *
+     * @param userId              the userId to retrieve the storage file
+     * @param fileName         the storage file name
+     * @return an AtomicFile for the user
+     */
+    @NonNull
+    static AtomicFile createStorageFileForUser(@UserIdInt int userId, String fileName) {
+        return new AtomicFile(getBaseStorageFileForUser(userId, fileName));
+    }
+
+    @NonNull
+    private static File getBaseStorageFileForUser(@UserIdInt int userId, String fileName) {
+        return new File(Environment.getDataSystemDeDirectory(userId), fileName);
+    }
+
+    /**
+     * Writing to file could fail, for example, if the user has been recently removed and so was
+     * their DE (/data/system_de/<user-id>/) directory.
+     */
+    static void writeToFileSafely(@NonNull AtomicFile file,
+            @NonNull FunctionalUtils.ThrowingConsumer<FileOutputStream> consumer) {
+        try {
+            file.write(consumer);
+        } catch (Exception e) {
+            Slog.e(LOG_TAG, "Error while writing to file " + file, e);
+        }
+    }
+
+    private DataStoreUtils() {
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 3c8c3cb..da33b44 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -25,9 +25,10 @@
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
-
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,7 +45,6 @@
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
 
-import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -53,7 +53,6 @@
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.HashSet;
@@ -210,6 +209,7 @@
             @NonNull Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         Slog.i(LOG_TAG, "Reading associations for user " + userId + " from disk");
+
         final AtomicFile file = getStorageFileForUser(userId);
         if (DEBUG) Slog.d(LOG_TAG, "  > File=" + file.getBaseFile().getPath());
 
@@ -349,11 +349,7 @@
      */
     private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
         return mUserIdToStorageFile.computeIfAbsent(userId,
-                u -> new AtomicFile(getBaseStorageFileForUser(userId)));
-    }
-
-    private static @NonNull File getBaseStorageFileForUser(@UserIdInt int userId) {
-        return new File(Environment.getDataSystemDeDirectory(userId), FILE_NAME);
+                u -> createStorageFileForUser(userId, FILE_NAME));
     }
 
     private static @NonNull File getBaseLegacyStorageFileForUser(@UserIdInt int userId) {
@@ -512,16 +508,6 @@
         serializer.endTag(null, XML_TAG_PACKAGE);
     }
 
-    private static boolean isStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
-            throws XmlPullParserException {
-        return parser.getEventType() == START_TAG && tag.equals(parser.getName());
-    }
-
-    private static boolean isEndOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
-            throws XmlPullParserException {
-        return parser.getEventType() == END_TAG && tag.equals(parser.getName());
-    }
-
     private static void requireStartOfTag(@NonNull XmlPullParser parser, @NonNull String tag)
             throws XmlPullParserException {
         if (isStartOfTag(parser, tag)) return;
@@ -546,13 +532,4 @@
         }
         return associationInfo;
     }
-
-    private static void writeToFileSafely(@NonNull AtomicFile file,
-            @NonNull ThrowingConsumer<FileOutputStream> consumer) {
-        try {
-            file.write(consumer);
-        } catch (Exception e) {
-            Slog.e(LOG_TAG, "Error while writing to file " + file, e);
-        }
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java b/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java
new file mode 100644
index 0000000..38e5d16
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/SystemDataTransferRequestDataStore.java
@@ -0,0 +1,226 @@
+/*
+ * 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.companion;
+
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readThisListXml;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeListXml;
+import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.companion.SystemDataTransferRequest;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * The class is responsible for reading/writing SystemDataTransferRequest records from/to the disk.
+ *
+ * The following snippet is a sample XML file stored in the disk.
+ * <pre>{@code
+ * <requests>
+ *   <request
+ *     association_id="1"
+ *     is_permission_sync_all_packages="false">
+ *     <list name="permission_sync_packages">
+ *       <string>com.sample.app1</string>
+ *       <string>com.sample.app2</string>
+ *     </list>
+ *   </request>
+ * </requests>
+ * }</pre>
+ */
+public class SystemDataTransferRequestDataStore {
+
+    private static final String LOG_TAG = SystemDataTransferRequestDataStore.class.getSimpleName();
+
+    private static final String FILE_NAME = "companion_device_system_data_transfer_requests.xml";
+
+    private static final String XML_TAG_REQUESTS = "requests";
+    private static final String XML_TAG_REQUEST = "request";
+    private static final String XML_TAG_LIST = "list";
+
+    private static final String XML_ATTR_ASSOCIATION_ID = "association_id";
+    private static final String XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES =
+            "is_permission_sync_all_packages";
+    private static final String XML_ATTR_PERMISSION_SYNC_PACKAGES = "permission_sync_packages";
+
+    private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Reads previously persisted data for the given user
+     *
+     * @param userId Android UserID
+     * @return a list of SystemDataTransferRequest
+     */
+    @NonNull
+    List<SystemDataTransferRequest> readRequestsForUser(@UserIdInt int userId) {
+        final AtomicFile file = getStorageFileForUser(userId);
+        Slog.i(LOG_TAG, "Reading SystemDataTransferRequests for user " + userId + " from "
+                + "file=" + file.getBaseFile().getPath());
+
+        // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+        // accesses to the file on the file system using this AtomicFile object.
+        synchronized (file) {
+            if (!file.getBaseFile().exists()) {
+                Slog.d(LOG_TAG, "File does not exist -> Abort");
+                return Collections.emptyList();
+            }
+            try (FileInputStream in = file.openRead()) {
+                final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
+
+                return readRequests(parser);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.e(LOG_TAG, "Error while reading requests file", e);
+                return Collections.emptyList();
+            }
+        }
+    }
+
+    @NonNull
+    private List<SystemDataTransferRequest> readRequests(@NonNull TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (!isStartOfTag(parser, XML_TAG_REQUESTS)) {
+            throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS);
+        }
+
+        List<SystemDataTransferRequest> requests = new ArrayList<>();
+
+        while (true) {
+            parser.nextTag();
+            if (isEndOfTag(parser, XML_TAG_REQUESTS)) break;
+            if (isStartOfTag(parser, XML_TAG_REQUEST)) {
+                requests.add(readRequest(parser));
+            }
+        }
+
+        return requests;
+    }
+
+    private SystemDataTransferRequest readRequest(@NonNull TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        if (!isStartOfTag(parser, XML_TAG_REQUEST)) {
+            throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST);
+        }
+
+        final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID);
+        final boolean isPermissionSyncAllPackages = readBooleanAttribute(parser,
+                XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES);
+        parser.nextTag();
+        List<String> permissionSyncPackages = new ArrayList<>();
+        if (isStartOfTag(parser, XML_TAG_LIST)) {
+            parser.nextTag();
+            permissionSyncPackages = readThisListXml(parser, XML_TAG_LIST,
+                    new String[1]);
+        }
+
+        return new SystemDataTransferRequest(associationId, isPermissionSyncAllPackages,
+                permissionSyncPackages);
+    }
+
+    /**
+     * Persisted user's SystemDataTransferRequest data to the disk.
+     *
+     * @param userId   Android UserID
+     * @param requests a list of user's SystemDataTransferRequest.
+     */
+    void writeRequestsForUser(@UserIdInt int userId,
+            @NonNull List<SystemDataTransferRequest> requests) {
+        final AtomicFile file = getStorageFileForUser(userId);
+        Slog.i(LOG_TAG, "Writing SystemDataTransferRequests for user " + userId + " to file="
+                + file.getBaseFile().getPath());
+
+        // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+        // accesses to the file on the file system using this AtomicFile object.
+        synchronized (file) {
+            writeToFileSafely(file, out -> {
+                final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+                serializer.setFeature(
+                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                serializer.startDocument(null, true);
+                writeRequests(serializer, requests);
+                serializer.endDocument();
+            });
+        }
+    }
+
+    private void writeRequests(@NonNull TypedXmlSerializer serializer,
+            @Nullable Collection<SystemDataTransferRequest> requests) throws IOException {
+        serializer.startTag(null, XML_TAG_REQUESTS);
+
+        for (SystemDataTransferRequest request : requests) {
+            writeRequest(serializer, request);
+        }
+
+        serializer.endTag(null, XML_TAG_REQUESTS);
+    }
+
+    private void writeRequest(@NonNull TypedXmlSerializer serializer,
+            @NonNull SystemDataTransferRequest request) throws IOException {
+        serializer.startTag(null, XML_TAG_REQUEST);
+
+        writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId());
+        writeBooleanAttribute(serializer, XML_ATTR_IS_PERMISSION_SYNC_ALL_PACKAGES,
+                request.isPermissionSyncAllPackages());
+        try {
+            writeListXml(request.getPermissionSyncPackages(), XML_ATTR_PERMISSION_SYNC_PACKAGES,
+                    serializer);
+        } catch (XmlPullParserException e) {
+            Slog.e(LOG_TAG, "Error writing permission sync packages into XML. "
+                    + request.getPermissionSyncPackages().toString());
+        }
+
+        serializer.endTag(null, XML_TAG_REQUEST);
+    }
+
+    /**
+     * Creates and caches {@link AtomicFile} object that represents the back-up file for the given
+     * user.
+     *
+     * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
+     * possible to synchronize reads and writes to the file using the returned object.
+     */
+    private @NonNull AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+        return mUserIdToStorageFile.computeIfAbsent(userId,
+                u -> createStorageFileForUser(userId, FILE_NAME));
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
new file mode 100644
index 0000000..0eb6b8d
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -0,0 +1,436 @@
+/*
+ * 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.companion.presence;
+
+import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED;
+import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
+import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
+import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
+import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON;
+import static android.bluetooth.BluetoothAdapter.STATE_ON;
+import static android.bluetooth.BluetoothAdapter.nameForState;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
+import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY;
+import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
+import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
+import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
+import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
+
+import static com.android.server.companion.presence.Utils.btDeviceToString;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.companion.AssociationInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.companion.AssociationStore;
+import com.android.server.companion.AssociationStore.ChangeType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SuppressLint("LongLogTag")
+class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "CompanionDevice_PresenceMonitor_BLE";
+
+    /**
+     * When using {@link ScanSettings#SCAN_MODE_LOW_POWER}, it usually takes from 20 seconds to
+     * 2 minutes for the BLE scanner to find advertisements sent from the same device.
+     * On the other hand, {@link android.bluetooth.BluetoothAdapter.LeScanCallback} will report
+     * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST} 10 sec after it finds the
+     * advertisement for the first time (add reports
+     * {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH FIRST_MATCH}).
+     * To avoid constantly reporting {@link Callback#onBleCompanionDeviceFound(int) onDeviceFound()}
+     * and {@link Callback#onBleCompanionDeviceLost(int) onDeviceLost()} (while the device is
+     * actually present) to its clients, {@link BleCompanionDeviceScanner}, will add 1-minute delay
+     * after it receives {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST}.
+     */
+    private static final int NOTIFY_DEVICE_LOST_DELAY = 2 * 60 * 1000; // 2 Min.
+
+    interface Callback {
+        void onBleCompanionDeviceFound(int associationId);
+
+        void onBleCompanionDeviceLost(int associationId);
+    }
+
+    private final @NonNull AssociationStore mAssociationStore;
+    private final @NonNull Callback mCallback;
+    private final @NonNull MainThreadHandler mMainThreadHandler;
+
+    // Non-null after init().
+    private @Nullable BluetoothAdapter mBtAdapter;
+    // Non-null after init() and when BLE is available. Otherwise - null.
+    private @Nullable BluetoothLeScanner mBleScanner;
+    // Only accessed from the Main thread.
+    private boolean mScanning = false;
+
+    BleCompanionDeviceScanner(
+            @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+        mAssociationStore = associationStore;
+        mCallback = callback;
+        mMainThreadHandler = new MainThreadHandler();
+    }
+
+    @MainThread
+    void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) {
+        if (DEBUG) Log.i(TAG, "init()");
+
+        if (mBtAdapter != null) {
+            throw new IllegalStateException(getClass().getSimpleName() + " is already initialized");
+        }
+        mBtAdapter = requireNonNull(btAdapter);
+
+        checkBleState();
+        registerBluetoothStateBroadcastReceiver(context);
+
+        mAssociationStore.registerListener(this);
+    }
+
+    @MainThread
+    final void restartScan() {
+        enforceInitialized();
+
+        if (DEBUG) Log.i(TAG , "restartScan()");
+        if (mBleScanner == null) {
+            if (DEBUG) Log.d(TAG, "  > BLE is not available");
+            return;
+        }
+
+        stopScanIfNeeded();
+        startScan();
+    }
+
+    @Override
+    public void onAssociationChanged(@ChangeType int changeType, AssociationInfo association) {
+        // Simply restart scanning.
+        if (Looper.getMainLooper().isCurrentThread()) {
+            restartScan();
+        } else {
+            mMainThreadHandler.post(this::restartScan);
+        }
+    }
+
+    @MainThread
+    private void checkBleState() {
+        enforceInitialized();
+
+        final boolean bleAvailable = isBleAvailable();
+        if (DEBUG) {
+            Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
+        }
+        if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) {
+            // Nothing changed.
+            if (DEBUG) Log.i(TAG, "  > BLE status did not change");
+            return;
+        }
+
+        if (bleAvailable) {
+            mBleScanner = mBtAdapter.getBluetoothLeScanner();
+            if (mBleScanner == null) {
+                // Oops, that's a race condition. Can return.
+                return;
+            }
+            if (DEBUG) Log.i(TAG, "  > BLE is now available");
+
+            startScan();
+        } else {
+            if (DEBUG) Log.i(TAG, "  > BLE is now unavailable");
+
+            stopScanIfNeeded();
+            mBleScanner = null;
+        }
+    }
+
+    /**
+     * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private
+     * access level, so it's not accessible from here.
+     */
+    private boolean isBleAvailable() {
+        final int state = mBtAdapter.getLeState();
+        if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state));
+        return state == STATE_ON || state == STATE_BLE_ON;
+    }
+
+    @MainThread
+    private void startScan() {
+        enforceInitialized();
+        // This method should not be called if scan is already in progress.
+        if (mScanning) throw new IllegalStateException("Scan is already in progress.");
+        // Neither should this method be called if the adapter is not available.
+        if (mBleScanner == null) throw new IllegalStateException("BLE is not available.");
+
+        if (DEBUG) Log.i(TAG, "startScan()");
+
+        // Collect MAC addresses from all associations.
+        final Set<String> macAddresses = new HashSet<>();
+        for (AssociationInfo association : mAssociationStore.getAssociations()) {
+            // Beware that BT stack does not consider low-case MAC addresses valid, while
+            // MacAddress.toString() return a low-case String.
+            final String macAddress = association.getDeviceMacAddressAsString();
+            if (macAddress != null) {
+                macAddresses.add(macAddress);
+            }
+        }
+        if (macAddresses.isEmpty()) {
+            if (DEBUG) Log.i(TAG, "  > there are no (associated) devices to Scan for.");
+            return;
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "  > addresses=(n=" + macAddresses.size() + ")"
+                        + "[" + String.join(", ", macAddresses) + "]");
+            }
+        }
+
+        final List<ScanFilter> filters = new ArrayList<>(macAddresses.size());
+        for (String macAddress : macAddresses) {
+            final ScanFilter filter = new ScanFilter.Builder()
+                    .setDeviceAddress(macAddress)
+                    .build();
+            filters.add(filter);
+        }
+
+        mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback);
+        mScanning = true;
+    }
+
+    private void stopScanIfNeeded() {
+        enforceInitialized();
+
+        if (DEBUG) Log.i(TAG, "stopScan()");
+        if (!mScanning) {
+            Log.d(TAG, "  > not scanning.");
+            return;
+        }
+
+        mBleScanner.stopScan(mScanCallback);
+        mScanning = false;
+    }
+
+    @MainThread
+    private void notifyDeviceFound(@NonNull BluetoothDevice device) {
+        if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
+
+        final List<AssociationInfo> associations =
+                mAssociationStore.getAssociationsByAddress(device.getAddress());
+        if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
+
+        for (AssociationInfo association : associations) {
+            mCallback.onBleCompanionDeviceFound(association.getId());
+        }
+    }
+
+    @MainThread
+    private void notifyDeviceLost(@NonNull BluetoothDevice device) {
+        if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
+
+        final List<AssociationInfo> associations =
+                mAssociationStore.getAssociationsByAddress(device.getAddress());
+        if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
+
+        for (AssociationInfo association : associations) {
+            mCallback.onBleCompanionDeviceLost(association.getId());
+        }
+    }
+
+    private void registerBluetoothStateBroadcastReceiver(Context context) {
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1);
+                final int state = intent.getIntExtra(EXTRA_STATE, -1);
+
+                if (DEBUG) {
+                    // The action is either STATE_CHANGED or BLE_STATE_CHANGED.
+                    final String action =
+                            intent.getAction().replace("android.bluetooth.adapter.", "bt.");
+                    Log.d(TAG, "on(Broadcast)Receive() " + action + ": "
+                            + nameForBtState(prevState) + "->" + nameForBtState(state));
+                }
+
+                checkBleState();
+            }
+        };
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_STATE_CHANGED);
+        filter.addAction(ACTION_BLE_STATE_CHANGED);
+
+        context.registerReceiver(receiver, filter);
+    }
+
+    private void enforceInitialized() {
+        if (mBtAdapter != null) return;
+        throw new IllegalStateException(getClass().getSimpleName() + " is not initialized");
+    }
+
+    private final ScanCallback mScanCallback = new ScanCallback() {
+        @MainThread
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            final BluetoothDevice device = result.getDevice();
+
+            if (DEBUG) {
+                Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType)
+                        + " device=" + btDeviceToString(device));
+                Log.v(TAG, "  > scanResult=" + result);
+
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getAssociationsByAddress(device.getAddress());
+                Log.v(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
+            }
+
+            switch (callbackType) {
+                case CALLBACK_TYPE_FIRST_MATCH:
+                    if (mMainThreadHandler.hasNotifyDeviceLostMessages(device)) {
+                        mMainThreadHandler.removeNotifyDeviceLostMessages(device);
+                        return;
+                    }
+
+                    notifyDeviceFound(device);
+                    break;
+
+                case CALLBACK_TYPE_MATCH_LOST:
+                    mMainThreadHandler.sendNotifyDeviceLostDelayedMessage(device);
+                    break;
+
+                default:
+                    Slog.wtf(TAG, "Unexpected callback "
+                            + nameForBleScanCallbackType(callbackType));
+                    break;
+            }
+        }
+
+        @MainThread
+        @Override
+        public void onScanFailed(int errorCode) {
+            if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode));
+            mScanning = false;
+        }
+    };
+
+    @SuppressLint("HandlerLeak")
+    private class MainThreadHandler extends Handler {
+        private static final int NOTIFY_DEVICE_LOST = 1;
+
+        MainThreadHandler() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message message) {
+            if  (message.what != NOTIFY_DEVICE_LOST) return;
+
+            final BluetoothDevice device = (BluetoothDevice) message.obj;
+            notifyDeviceLost(device);
+        }
+
+        void sendNotifyDeviceLostDelayedMessage(BluetoothDevice device) {
+            final Message message = obtainMessage(NOTIFY_DEVICE_LOST, device);
+            sendMessageDelayed(message, NOTIFY_DEVICE_LOST_DELAY);
+        }
+
+        boolean hasNotifyDeviceLostMessages(BluetoothDevice device) {
+            return hasEqualMessages(NOTIFY_DEVICE_LOST, device);
+        }
+
+        void removeNotifyDeviceLostMessages(BluetoothDevice device) {
+            removeEqualMessages(NOTIFY_DEVICE_LOST, device);
+        }
+    }
+
+    private static String nameForBtState(int state) {
+        return nameForState(state) + "(" + state + ")";
+    }
+
+    private static String nameForBleScanCallbackType(int callbackType) {
+        final String name;
+        switch (callbackType) {
+            case CALLBACK_TYPE_ALL_MATCHES:
+                name = "ALL_MATCHES";
+                break;
+            case CALLBACK_TYPE_FIRST_MATCH:
+                name = "FIRST_MATCH";
+                break;
+            case CALLBACK_TYPE_MATCH_LOST:
+                name = "MATCH_LOST";
+                break;
+            default:
+                name = "Unknown";
+        }
+        return name + "(" + callbackType + ")";
+    }
+
+    private static String nameForBleScanErrorCode(int errorCode) {
+        final String name;
+        switch (errorCode) {
+            case SCAN_FAILED_ALREADY_STARTED:
+                name = "ALREADY_STARTED";
+                break;
+            case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
+                name = "APPLICATION_REGISTRATION_FAILED";
+                break;
+            case SCAN_FAILED_INTERNAL_ERROR:
+                name = "INTERNAL_ERROR";
+                break;
+            case SCAN_FAILED_FEATURE_UNSUPPORTED:
+                name = "FEATURE_UNSUPPORTED";
+                break;
+            case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
+                name = "OUT_OF_HARDWARE_RESOURCES";
+                break;
+            case SCAN_FAILED_SCANNING_TOO_FREQUENTLY:
+                name = "SCANNING_TOO_FREQUENTLY";
+                break;
+            default:
+                name = "Unknown";
+        }
+        return name + "(" + errorCode + ")";
+    }
+
+    private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder()
+            .setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
+            .setScanMode(SCAN_MODE_LOW_POWER)
+            .build();
+}
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
new file mode 100644
index 0000000..dbe866b
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -0,0 +1,166 @@
+/*
+ * 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.companion.presence;
+
+import static com.android.server.companion.presence.Utils.btDeviceToString;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationInfo;
+import android.net.MacAddress;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.util.Log;
+
+import com.android.server.companion.AssociationStore;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressLint("LongLogTag")
+class BluetoothCompanionDeviceConnectionListener
+        extends BluetoothAdapter.BluetoothConnectionCallback
+        implements AssociationStore.OnChangeListener {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "CompanionDevice_PresenceMonitor_BT";
+
+    interface Callback {
+        void onBluetoothCompanionDeviceConnected(int associationId);
+
+        void onBluetoothCompanionDeviceDisconnected(int associationId);
+    }
+
+    private final @NonNull AssociationStore mAssociationStore;
+    private final @NonNull Callback mCallback;
+    /** A set of ALL connected BT device (not only companion.) */
+    private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+
+    BluetoothCompanionDeviceConnectionListener(@NonNull AssociationStore associationStore,
+            @NonNull Callback callback) {
+        mAssociationStore = associationStore;
+        mCallback = callback;
+    }
+
+    public void init(@NonNull BluetoothAdapter btAdapter) {
+        if (DEBUG) Log.i(TAG, "init()");
+
+        btAdapter.registerBluetoothConnectionCallback(
+                new HandlerExecutor(Handler.getMain()), /* callback */this);
+        mAssociationStore.registerListener(this);
+    }
+
+    /**
+     * Overrides
+     * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}.
+     */
+    @Override
+    public void onDeviceConnected(@NonNull BluetoothDevice device) {
+        if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
+
+        final MacAddress macAddress = MacAddress.fromString(device.getAddress());
+        if (mAllConnectedDevices.put(macAddress, device) != null) {
+            if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
+            return;
+        }
+
+        onDeviceConnectivityChanged(device, true);
+    }
+
+    /**
+     * Overrides
+     * {@link BluetoothAdapter.BluetoothConnectionCallback#onDeviceConnected(BluetoothDevice)}.
+     * Also invoked when user turns BT off while the device is connected.
+     */
+    @Override
+    public void onDeviceDisconnected(@NonNull BluetoothDevice device,
+            @DisconnectReason int reason) {
+        if (DEBUG) {
+            Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
+            Log.d(TAG, "  reason=" + disconnectReasonText(reason));
+        }
+
+        final MacAddress macAddress = MacAddress.fromString(device.getAddress());
+        if (mAllConnectedDevices.remove(macAddress) == null) {
+            if (DEBUG) {
+                Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
+            }
+            return;
+        }
+
+        onDeviceConnectivityChanged(device, false);
+    }
+
+    private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
+        final List<AssociationInfo> associations =
+                mAssociationStore.getAssociationsByAddress(device.getAddress());
+
+        if (DEBUG) {
+            Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
+                    + " connected=" + connected);
+            if (associations.isEmpty()) {
+                Log.d(TAG, "  > No CDM associations");
+            } else {
+                Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
+            }
+        }
+
+        for (AssociationInfo association : associations) {
+            final int id = association.getId();
+            if (connected) {
+                mCallback.onBluetoothCompanionDeviceConnected(id);
+            } else {
+                mCallback.onBluetoothCompanionDeviceDisconnected(id);
+            }
+        }
+    }
+
+    @Override
+    public void onAssociationAdded(AssociationInfo association) {
+        if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
+
+        if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
+            mCallback.onBluetoothCompanionDeviceConnected(association.getId());
+        }
+    }
+
+    @Override
+    public void onAssociationRemoved(AssociationInfo association) {
+        // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping
+        // required.
+    }
+
+    @Override
+    public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
+        if (DEBUG) {
+            Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged
+                    + " " + association);
+        }
+
+        if (!addressChanged) {
+            // Don't need to do anything.
+            return;
+        }
+
+        // At the moment CDM does allow changing association addresses, so we will never come here.
+        // This will be implemented when CDM support updating addresses.
+        throw new IllegalArgumentException("Address changes are not supported.");
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java
new file mode 100644
index 0000000..583b443
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/Utils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.companion.presence;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothDevice;
+
+/** Utilities for working with Bluetooth and BLE devices. */
+class Utils {
+
+    /**
+     * @return short String representation of {@link BluetoothDevice}.
+     */
+    static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
+        final StringBuilder sb = new StringBuilder(btDevice.getAddress());
+
+        sb.append(" [name=");
+        final String name = btDevice.getName();
+        if (name != null) {
+            sb.append('\'').append(name).append('\'');
+        } else {
+            sb.append("null");
+        }
+
+        final String alias = btDevice.getAlias();
+        if (alias != null) {
+            sb.append(", alias='").append(alias).append("'");
+        }
+
+        return sb.append(']').toString();
+    }
+
+    private Utils() {
+    }
+}
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 734e5c3..75acf81 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -21,18 +21,24 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 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;
+import java.util.Set;
 
 
 /**
@@ -49,14 +55,38 @@
     @ChangeId
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
-    @NonNull private final ArraySet<UserHandle> mAllowedUsers;
+    @NonNull
+    private final ArraySet<UserHandle> mAllowedUsers;
+    @Nullable
+    private final ArraySet<ComponentName> mAllowedActivities;
+    @Nullable
+    private final ArraySet<ComponentName> mBlockedActivities;
 
-    @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();
+    @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) {
+            @NonNull ArraySet<UserHandle> allowedUsers,
+            @Nullable Set<ComponentName> allowedActivities,
+            @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
@@ -80,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));
+        }
     }
 
     /**
@@ -108,6 +146,18 @@
             Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
             return false;
         }
+        if (mBlockedActivities != null
+                && mBlockedActivities.contains(activityInfo.getComponentName())) {
+            Slog.d(TAG,
+                    "Virtual device blocking launch of " + activityInfo.getComponentName());
+            return false;
+        }
+        if (mAllowedActivities != null
+                && !mAllowedActivities.contains(activityInfo.getComponentName())) {
+            Slog.d(TAG,
+                    activityInfo.getComponentName() + " is not in the allowed list.");
+            return false;
+        }
         if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
                 activityInfo.packageName, activityUser)) {
             // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
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 067edcc..6c56e2f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -16,35 +16,54 @@
 
 package com.android.server.companion.virtual;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.graphics.Point;
+import android.graphics.PointF;
+import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
 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;
+import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 
 /** Controls virtual input devices, including device lifecycle and event dispatch. */
 class InputController {
 
+    private static final String TAG = "VirtualInputController";
+
     private final Object mLock;
 
     /* Token -> file descriptor associations. */
     @VisibleForTesting
     @GuardedBy("mLock")
-    final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>();
+    final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
     private final NativeWrapper mNativeWrapper;
 
+    /**
+     * Because the pointer is a singleton, it can only be targeted at one display at a time. Because
+     * multiple mice could be concurrently registered, mice that are associated with a different
+     * display than the current target display should not be allowed to affect the current target.
+     */
+    @VisibleForTesting int mActivePointerDisplayId;
+
     InputController(@NonNull Object lock) {
         this(lock, new NativeWrapper());
     }
@@ -53,32 +72,39 @@
     InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
         mLock = lock;
         mNativeWrapper = nativeWrapper;
+        mActivePointerDisplayId = Display.INVALID_DISPLAY;
     }
 
     void close() {
         synchronized (mLock) {
-            for (int fd : mInputDeviceFds.values()) {
-                mNativeWrapper.closeUinput(fd);
+            for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) {
+                mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
             }
-            mInputDeviceFds.clear();
+            mInputDeviceDescriptors.clear();
+            resetMouseValuesLocked();
         }
     }
 
     void createKeyboard(@NonNull String deviceName,
             int vendorId,
             int productId,
-            @NonNull IBinder deviceToken) {
+            @NonNull IBinder deviceToken,
+            int displayId) {
         final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating keyboard: " + -fd);
         }
+        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
         synchronized (mLock) {
-            mInputDeviceFds.put(deviceToken, fd);
+            mInputDeviceDescriptors.put(deviceToken,
+                    new InputDeviceDescriptor(fd, binderDeathRecipient,
+                            InputDeviceDescriptor.TYPE_KEYBOARD, displayId));
         }
         try {
-            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
+            // TODO(b/215608394): remove and close InputDeviceDescriptor
             throw new RuntimeException("Could not create virtual keyboard", e);
         }
     }
@@ -86,18 +112,28 @@
     void createMouse(@NonNull String deviceName,
             int vendorId,
             int productId,
-            @NonNull IBinder deviceToken) {
+            @NonNull IBinder deviceToken,
+            int displayId) {
         final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
         if (fd < 0) {
             throw new RuntimeException(
                     "A native error occurred when creating mouse: " + -fd);
         }
+        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
         synchronized (mLock) {
-            mInputDeviceFds.put(deviceToken, fd);
+            mInputDeviceDescriptors.put(deviceToken,
+                    new InputDeviceDescriptor(fd, binderDeathRecipient,
+                            InputDeviceDescriptor.TYPE_MOUSE, displayId));
+            final InputManagerInternal inputManagerInternal =
+                    LocalServices.getService(InputManagerInternal.class);
+            inputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+            inputManagerInternal.setPointerAcceleration(1);
+            mActivePointerDisplayId = displayId;
         }
         try {
-            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
+            // TODO(b/215608394): remove and close InputDeviceDescriptor
             throw new RuntimeException("Could not create virtual mouse", e);
         }
     }
@@ -106,6 +142,7 @@
             int vendorId,
             int productId,
             @NonNull IBinder deviceToken,
+            int displayId,
             @NonNull Point screenSize) {
         final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
                 screenSize.y, screenSize.x);
@@ -113,93 +150,179 @@
             throw new RuntimeException(
                     "A native error occurred when creating touchscreen: " + -fd);
         }
+        final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
         synchronized (mLock) {
-            mInputDeviceFds.put(deviceToken, fd);
+            mInputDeviceDescriptors.put(deviceToken,
+                    new InputDeviceDescriptor(fd, binderDeathRecipient,
+                            InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId));
         }
         try {
-            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+            deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
         } catch (RemoteException e) {
+            // TODO(b/215608394): remove and close InputDeviceDescriptor
             throw new RuntimeException("Could not create virtual touchscreen", e);
         }
     }
 
     void unregisterInputDevice(@NonNull IBinder token) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.remove(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not unregister input device for given token");
             }
-            mNativeWrapper.closeUinput(fd);
+            token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
+            mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+
+            // Reset values to the default if all virtual mice are unregistered, or set display
+            // id if there's another mouse (choose the most recent).
+            if (inputDeviceDescriptor.isMouse()) {
+                updateMouseValuesLocked();
+            }
         }
     }
 
+    @GuardedBy("mLock")
+    private void updateMouseValuesLocked() {
+        InputDeviceDescriptor mostRecentlyCreatedMouse = null;
+        for (InputDeviceDescriptor otherInputDeviceDescriptor :
+                mInputDeviceDescriptors.values()) {
+            if (otherInputDeviceDescriptor.isMouse()) {
+                if (mostRecentlyCreatedMouse == null
+                        || (otherInputDeviceDescriptor.getCreationOrderNumber()
+                        > mostRecentlyCreatedMouse.getCreationOrderNumber())) {
+                    mostRecentlyCreatedMouse = otherInputDeviceDescriptor;
+                }
+            }
+        }
+        if (mostRecentlyCreatedMouse != null) {
+            final InputManagerInternal inputManagerInternal =
+                    LocalServices.getService(InputManagerInternal.class);
+            inputManagerInternal.setVirtualMousePointerDisplayId(
+                    mostRecentlyCreatedMouse.getDisplayId());
+            mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId();
+        } else {
+            // All mice have been unregistered; reset all values.
+            resetMouseValuesLocked();
+        }
+    }
+
+    private void resetMouseValuesLocked() {
+        final InputManagerInternal inputManagerInternal =
+                LocalServices.getService(InputManagerInternal.class);
+        inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+        inputManagerInternal.setPointerAcceleration(
+                IInputConstants.DEFAULT_POINTER_ACCELERATION);
+        mActivePointerDisplayId = Display.INVALID_DISPLAY;
+    }
+
     boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.get(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not send key event to input device for given token");
             }
-            return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction());
+            return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getKeyCode(), event.getAction());
         }
     }
 
     boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.get(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not send button event to input device for given token");
             }
-            return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction());
+            if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) {
+                throw new IllegalStateException(
+                        "Display id associated with this mouse is not currently targetable");
+            }
+            return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getButtonCode(), event.getAction());
         }
     }
 
     boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.get(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not send touch event to input device for given token");
             }
-            return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(),
-                    event.getAction(), event.getX(), event.getY(), event.getPressure(),
-                    event.getMajorAxisSize());
+            return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getPointerId(), event.getToolType(), event.getAction(), event.getX(),
+                    event.getY(), event.getPressure(), event.getMajorAxisSize());
         }
     }
 
     boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.get(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not send relative event to input device for given token");
             }
-            return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(),
-                    event.getRelativeY());
+            if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) {
+                throw new IllegalStateException(
+                        "Display id associated with this mouse is not currently targetable");
+            }
+            return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getRelativeX(), event.getRelativeY());
         }
     }
 
     boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) {
         synchronized (mLock) {
-            final Integer fd = mInputDeviceFds.get(token);
-            if (fd == null) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
                 throw new IllegalArgumentException(
                         "Could not send scroll event to input device for given token");
             }
-            return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(),
-                    event.getYAxisMovement());
+            if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) {
+                throw new IllegalStateException(
+                        "Display id associated with this mouse is not currently targetable");
+            }
+            return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(),
+                    event.getXAxisMovement(), event.getYAxisMovement());
+        }
+    }
+
+    public PointF getCursorPosition(@NonNull IBinder token) {
+        synchronized (mLock) {
+            final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
+                    token);
+            if (inputDeviceDescriptor == null) {
+                throw new IllegalArgumentException(
+                        "Could not get cursor position for input device for given token");
+            }
+            if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) {
+                throw new IllegalStateException(
+                        "Display id associated with this mouse is not currently targetable");
+            }
+            return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
         }
     }
 
     public void dump(@NonNull PrintWriter fout) {
         fout.println("    InputController: ");
         synchronized (mLock) {
-            fout.println("      Active file descriptors: ");
-            for (int inputDeviceFd : mInputDeviceFds.values()) {
-                fout.println(inputDeviceFd);
+            fout.println("      Active descriptors: ");
+            for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) {
+                fout.println("        fd: " + inputDeviceDescriptor.getFileDescriptor());
+                fout.println("          displayId: " + inputDeviceDescriptor.getDisplayId());
+                fout.println("          creationOrder: "
+                        + inputDeviceDescriptor.getCreationOrderNumber());
+                fout.println("          type: " + inputDeviceDescriptor.getType());
             }
+            fout.println("      Active mouse display id: " + mActivePointerDisplayId);
         }
     }
 
@@ -267,6 +390,63 @@
         }
     }
 
+    @VisibleForTesting static final class InputDeviceDescriptor {
+
+        static final int TYPE_KEYBOARD = 1;
+        static final int TYPE_MOUSE = 2;
+        static final int TYPE_TOUCHSCREEN = 3;
+        @IntDef(prefix = { "TYPE_" }, value = {
+                TYPE_KEYBOARD,
+                TYPE_MOUSE,
+                TYPE_TOUCHSCREEN,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface Type {
+        }
+
+        private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1);
+
+        private final int mFd;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        private final @Type int mType;
+        private final int mDisplayId;
+        // Monotonically increasing number; devices with lower numbers were created earlier.
+        private final long mCreationOrderNumber;
+
+        InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient,
+                @Type int type, int displayId) {
+            mFd = fd;
+            mDeathRecipient = deathRecipient;
+            mType = type;
+            mDisplayId = displayId;
+            mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
+        }
+
+        public int getFileDescriptor() {
+            return mFd;
+        }
+
+        public int getType() {
+            return mType;
+        }
+
+        public boolean isMouse() {
+            return mType == TYPE_MOUSE;
+        }
+
+        public IBinder.DeathRecipient getDeathRecipient() {
+            return mDeathRecipient;
+        }
+
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        public long getCreationOrderNumber() {
+            return mCreationOrderNumber;
+        }
+    }
+
     private final class BinderDeathRecipient implements IBinder.DeathRecipient {
 
         private final IBinder mDeviceToken;
@@ -277,6 +457,10 @@
 
         @Override
         public void binderDied() {
+            // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before
+            // quitting, which removes this death recipient. If this is invoked, the remote end
+            // died, or they disposed of the object without properly unregistering.
+            Slog.e(TAG, "Virtual input controller binder died");
             unregisterInputDevice(mDeviceToken);
         }
     }
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 0c0ee52..95b9e58 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,10 +29,15 @@
 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;
 import android.hardware.display.DisplayManager;
+import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -40,21 +45,24 @@
 import android.hardware.input.VirtualTouchEvent;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 
 final class VirtualDeviceImpl extends IVirtualDevice.Stub
@@ -69,10 +77,35 @@
     private final int mOwnerUid;
     private final InputController mInputController;
     @VisibleForTesting
-    final List<Integer> mVirtualDisplayIds = new ArrayList<>();
+    final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
     private final OnDeviceCloseListener mListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
+    private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
+    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
@@ -83,18 +116,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;
@@ -173,6 +210,16 @@
 
     @Override // Binder call
     public void close() {
+        synchronized (mVirtualDeviceLock) {
+            if (!mPerDisplayWakelocks.isEmpty()) {
+                mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
+                    Slog.w(TAG, "VirtualDisplay " + displayId + " owned by UID " + mOwnerUid
+                            + " was not properly released");
+                    wakeLock.release();
+                });
+                mPerDisplayWakelocks.clear();
+            }
+        }
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
         mInputController.close();
@@ -202,7 +249,8 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken);
+            mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
+                    displayId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -227,7 +275,7 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            mInputController.createMouse(deviceName, vendorId, productId, deviceToken);
+            mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -254,7 +302,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             mInputController.createTouchscreen(deviceName, vendorId, productId,
-                    deviceToken, screenSize);
+                    deviceToken, displayId, screenSize);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -324,10 +372,21 @@
         }
     }
 
+    @Override // Binder call
+    public PointF getCursorPosition(IBinder token) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.getCursorPosition(token);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         fout.println("  VirtualDevice: ");
         fout.println("    mAssociationId: " + mAssociationInfo.getId());
+        fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
             for (int id : mVirtualDisplayIds) {
@@ -338,16 +397,48 @@
     }
 
     DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
-        if (mVirtualDisplayIds.contains(displayId)) {
-            throw new IllegalStateException(
-                    "Virtual device already have a virtual display with ID " + displayId);
+        synchronized (mVirtualDeviceLock) {
+            if (mVirtualDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device already have a virtual display with ID " + displayId);
+            }
+            mVirtualDisplayIds.add(displayId);
+
+            // Since we're being called in the middle of the display being created, we post a
+            // task to grab the wakelock instead of doing it synchronously here, to avoid
+            // reentrancy  problems.
+            mContext.getMainThreadHandler().post(() -> addWakeLockForDisplay(displayId));
+
+            LocalServices.getService(
+                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(displayId,
+                    false);
+            final GenericWindowPolicyController dwpc =
+                    new GenericWindowPolicyController(FLAG_SECURE,
+                            SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                            getAllowedUserHandles(),
+                            mParams.getAllowedActivities(),
+                            mParams.getBlockedActivities(),
+                            createListenerAdapter(displayId));
+            mWindowPolicyControllers.put(displayId, dwpc);
+            return dwpc;
         }
-        mVirtualDisplayIds.add(displayId);
-        final GenericWindowPolicyController dwpc =
-                new GenericWindowPolicyController(FLAG_SECURE,
-                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
-        mWindowPolicyControllers.put(displayId, dwpc);
-        return dwpc;
+    }
+
+    void addWakeLockForDisplay(int displayId) {
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)
+                    || mPerDisplayWakelocks.containsKey(displayId)) {
+                Slog.e(TAG, "Not creating wakelock for displayId " + displayId);
+                return;
+            }
+            PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
+                    PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+                            | PowerManager.ACQUIRE_CAUSES_WAKEUP,
+                    TAG + ":" + displayId, displayId);
+            wakeLock.acquire();
+            mPerDisplayWakelocks.put(displayId, wakeLock);
+        }
     }
 
     private ArraySet<UserHandle> getAllowedUserHandles() {
@@ -369,12 +460,22 @@
     }
 
     void onVirtualDisplayRemovedLocked(int displayId) {
-        if (!mVirtualDisplayIds.contains(displayId)) {
-            throw new IllegalStateException(
-                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new IllegalStateException(
+                        "Virtual device doesn't have a virtual display with ID " + displayId);
+            }
+            PowerManager.WakeLock wakeLock = mPerDisplayWakelocks.get(displayId);
+            if (wakeLock != null) {
+                wakeLock.release();
+                mPerDisplayWakelocks.remove(displayId);
+            }
+            mVirtualDisplayIds.remove(displayId);
+            LocalServices.getService(
+                    InputManagerInternal.class).setDisplayEligibilityForPointerCapture(
+                    displayId, true);
+            mWindowPolicyControllers.remove(displayId);
         }
-        mVirtualDisplayIds.remove(displayId);
-        mWindowPolicyControllers.remove(displayId);
     }
 
     int getOwnerUid() {
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/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 60cae4d..f56bfab 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -70,6 +70,7 @@
             PACKAGE_SYSTEM,
             PACKAGE_SETUP_WIZARD,
             PACKAGE_INSTALLER,
+            PACKAGE_UNINSTALLER,
             PACKAGE_VERIFIER,
             PACKAGE_BROWSER,
             PACKAGE_SYSTEM_TEXT_CLASSIFIER,
@@ -89,23 +90,25 @@
     public static final int PACKAGE_SYSTEM = 0;
     public static final int PACKAGE_SETUP_WIZARD = 1;
     public static final int PACKAGE_INSTALLER = 2;
-    public static final int PACKAGE_VERIFIER = 3;
-    public static final int PACKAGE_BROWSER = 4;
-    public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 5;
-    public static final int PACKAGE_PERMISSION_CONTROLLER = 6;
-    public static final int PACKAGE_WELLBEING = 7;
-    public static final int PACKAGE_DOCUMENTER = 8;
-    public static final int PACKAGE_CONFIGURATOR = 9;
-    public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
-    public static final int PACKAGE_APP_PREDICTOR = 11;
-    public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 12;
-    public static final int PACKAGE_WIFI = 13;
-    public static final int PACKAGE_COMPANION = 14;
-    public static final int PACKAGE_RETAIL_DEMO = 15;
-    public static final int PACKAGE_RECENTS = 16;
+    public static final int PACKAGE_UNINSTALLER = 3;
+    public static final int PACKAGE_VERIFIER = 4;
+    public static final int PACKAGE_BROWSER = 5;
+    public static final int PACKAGE_SYSTEM_TEXT_CLASSIFIER = 6;
+    public static final int PACKAGE_PERMISSION_CONTROLLER = 7;
+    public static final int PACKAGE_WELLBEING = 8;
+    public static final int PACKAGE_DOCUMENTER = 9;
+    public static final int PACKAGE_CONFIGURATOR = 10;
+    public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 11;
+    public static final int PACKAGE_APP_PREDICTOR = 12;
+    public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 13;
+    public static final int PACKAGE_WIFI = 14;
+    public static final int PACKAGE_COMPANION = 15;
+    public static final int PACKAGE_RETAIL_DEMO = 16;
+    public static final int PACKAGE_RECENTS = 17;
+    public static final int PACKAGE_AMBIENT_CONTEXT_DETECTION = 18;
     // Integer value of the last known package ID. Increases as new ID is added to KnownPackage.
     // Please note the numbers should be continuous.
-    public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS;
+    public static final int LAST_KNOWN_PACKAGE = PACKAGE_AMBIENT_CONTEXT_DETECTION;
 
     @LongDef(flag = true, prefix = "RESOLVE_", value = {
             RESOLVE_NON_BROWSER_ONLY,
@@ -1141,6 +1144,8 @@
                 return "Setup Wizard";
             case PACKAGE_INSTALLER:
                 return "Installer";
+            case PACKAGE_UNINSTALLER:
+                return "Uninstaller";
             case PACKAGE_VERIFIER:
                 return "Verifier";
             case PACKAGE_BROWSER:
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 844ac86..5d48d78 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -853,7 +853,9 @@
         pw.println("Battery service (battery) commands:");
         pw.println("  help");
         pw.println("    Print this help text.");
-        pw.println("  set [-f] [ac|usb|wireless|status|level|temp|present|invalid] <value>");
+        pw.println("  get [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid]");
+        pw.println(
+                "  set [-f] [ac|usb|wireless|status|level|temp|present|counter|invalid] <value>");
         pw.println("    Force a battery property value, freezing battery state.");
         pw.println("    -f: force a battery change broadcast be sent, prints new sequence.");
         pw.println("  unplug [-f]");
@@ -863,7 +865,7 @@
         pw.println("    Unfreeze battery state, returning to current hardware values.");
         pw.println("    -f: force a battery change broadcast be sent, prints new sequence.");
         if (Build.IS_DEBUGGABLE) {
-            pw.println("  disable_charge");
+            pw.println("  suspend_input");
             pw.println("    Suspend charging even if plugged in. ");
         }
     }
@@ -893,6 +895,46 @@
                         android.Manifest.permission.DEVICE_POWER, null);
                 unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw);
             } break;
+            case "get": {
+                final String key = shell.getNextArg();
+                if (key == null) {
+                    pw.println("No property specified");
+                    return -1;
+
+                }
+                switch (key) {
+                    case "present":
+                        pw.println(mHealthInfo.batteryPresent);
+                        break;
+                    case "ac":
+                        pw.println(mHealthInfo.chargerAcOnline);
+                        break;
+                    case "usb":
+                        pw.println(mHealthInfo.chargerUsbOnline);
+                        break;
+                    case "wireless":
+                        pw.println(mHealthInfo.chargerWirelessOnline);
+                        break;
+                    case "status":
+                        pw.println(mHealthInfo.batteryStatus);
+                        break;
+                    case "level":
+                        pw.println(mHealthInfo.batteryLevel);
+                        break;
+                    case "counter":
+                        pw.println(mHealthInfo.batteryChargeCounterUah);
+                        break;
+                    case "temp":
+                        pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
+                        break;
+                    case "invalid":
+                        pw.println(mInvalidCharger);
+                        break;
+                    default:
+                        pw.println("Unknown get option: " + key);
+                        break;
+                }
+            } break;
             case "set": {
                 int opts = parseOptions(shell);
                 getContext().enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 62dc2733..be2b7f7 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -16,11 +16,14 @@
 
 package com.android.server;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE;
+
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.ProgressDialog;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -155,7 +158,7 @@
         final Notification notification =
                 new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
-                        .setContentTitle(context.getString(R.string.work_profile_deleted))
+                        .setContentTitle(getWorkProfileDeletedTitle(context))
                         .setContentText(wipeReason)
                         .setColor(context.getColor(R.color.system_notification_accent_color))
                         .setStyle(new Notification.BigTextStyle().bigText(wipeReason))
@@ -164,6 +167,12 @@
                 SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification);
     }
 
+    private String getWorkProfileDeletedTitle(Context context) {
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(WORK_PROFILE_DELETED_TITLE,
+                () -> context.getString(R.string.work_profile_deleted));
+    }
+
     private @UserIdInt int getCurrentForegroundUserId() {
         try {
             return ActivityManager.getCurrentUser();
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 98764e0..f71f02a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -160,8 +160,6 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
 
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -173,6 +171,8 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.PrintWriter;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
@@ -3698,16 +3698,29 @@
     @Nullable
     public PendingIntent getManageSpaceActivityIntent(
             @NonNull String packageName, int requestCode) {
-        // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API.
-        enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE);
-
-        // We want to call the manageSpaceActivity as a SystemService and clear identity
-        // of the calling App
+        // Only Apps with MANAGE_EXTERNAL_STORAGE permission which have package visibility for
+        // packageName should be able to call this API.
         int originalUid = Binder.getCallingUidOrThrow();
-        final long token = Binder.clearCallingIdentity();
-
         try {
-            ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0,
+            // Get package name for calling app and verify it has MANAGE_EXTERNAL_STORAGE permission
+            final String[] packagesFromUid = mIPackageManager.getPackagesForUid(originalUid);
+            if (packagesFromUid == null) {
+                throw new SecurityException("Unknown uid " + originalUid);
+            }
+            // Checking first entry in packagesFromUid is enough as using "sharedUserId"
+            // mechanism is rare and discouraged. Also, Apps that share same UID share the same
+            // permissions.
+            if (!mStorageManagerInternal.hasExternalStorageAccess(originalUid,
+                    packagesFromUid[0])) {
+                throw new SecurityException("Only File Manager Apps permitted");
+            }
+        } catch (RemoteException re) {
+            throw new SecurityException("Unknown uid " + originalUid, re);
+        }
+
+        ApplicationInfo appInfo;
+        try {
+            appInfo = mIPackageManager.getApplicationInfo(packageName, 0,
                     UserHandle.getUserId(originalUid));
             if (appInfo == null) {
                 throw new IllegalArgumentException(
@@ -3717,8 +3730,15 @@
                 Log.i(TAG, packageName + " doesn't have a manageSpaceActivity");
                 return null;
             }
-            Context targetAppContext = mContext.createPackageContext(packageName, 0);
+        } catch (RemoteException e) {
+            throw new SecurityException("Only File Manager Apps permitted");
+        }
 
+        // We want to call the manageSpaceActivity as a SystemService and clear identity
+        // of the calling App
+        final long token = Binder.clearCallingIdentity();
+        try {
+            Context targetAppContext = mContext.createPackageContext(packageName, 0);
             Intent intent = new Intent(Intent.ACTION_DEFAULT);
             intent.setClassName(packageName,
                     appInfo.manageSpaceActivityName);
@@ -3728,8 +3748,6 @@
                     intent,
                     FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
             return activity;
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
         } catch (PackageManager.NameNotFoundException e) {
             throw new IllegalArgumentException(
                     "packageName not found");
@@ -4955,19 +4973,17 @@
         @Override
         public boolean hasExternalStorageAccess(int uid, String packageName) {
             try {
-                if (mIPackageManager.checkUidPermission(
-                                MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) {
-                    return true;
+                final int opMode = mIAppOpsService.checkOperation(
+                        OP_MANAGE_EXTERNAL_STORAGE, uid, packageName);
+                if (opMode == AppOpsManager.MODE_DEFAULT) {
+                    return mIPackageManager.checkUidPermission(
+                            MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED;
                 }
 
-                if (mIAppOpsService.checkOperation(
-                                OP_MANAGE_EXTERNAL_STORAGE, uid, packageName) == MODE_ALLOWED) {
-                    return true;
-                }
+                return opMode == AppOpsManager.MODE_ALLOWED;
             } catch (RemoteException e) {
                 Slog.w("Failed to check MANAGE_EXTERNAL_STORAGE access for " + packageName, e);
             }
-
             return false;
         }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e040319..8887108 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4138,7 +4138,7 @@
             // for a previous process to come up.  To deal with this, we store
             // in the service any current isolated process it is running in or
             // waiting to have come up.
-            app = r.isolatedProc;
+            app = r.isolationHostProc;
             if (WebViewZygote.isMultiprocessEnabled()
                     && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
                 hostingRecord = HostingRecord.byWebviewZygote(r.instanceName);
@@ -4165,7 +4165,7 @@
                 return msg;
             }
             if (isolated) {
-                r.isolatedProc = app;
+                r.isolationHostProc = app;
             }
         }
 
@@ -4976,7 +4976,7 @@
             try {
                 for (int i=0; i<mPendingServices.size(); i++) {
                     sr = mPendingServices.get(i);
-                    if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
+                    if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid
                             || !processName.equals(sr.processName))) {
                         continue;
                     }
@@ -5016,7 +5016,7 @@
             boolean didImmediateRestart = false;
             for (int i=0; i<mRestartingServices.size(); i++) {
                 sr = mRestartingServices.get(i);
-                if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
+                if (proc != sr.isolationHostProc && (proc.uid != sr.appInfo.uid
                         || !processName.equals(sr.processName))) {
                     continue;
                 }
@@ -5048,9 +5048,9 @@
             ServiceRecord sr = mPendingServices.get(i);
             if ((proc.uid == sr.appInfo.uid
                     && proc.processName.equals(sr.processName))
-                    || sr.isolatedProc == proc) {
+                    || sr.isolationHostProc == proc) {
                 Slog.w(TAG, "Forcing bringing down service: " + sr);
-                sr.isolatedProc = null;
+                sr.isolationHostProc = null;
                 mPendingServices.remove(i);
                 size = mPendingServices.size();
                 i--;
@@ -5083,7 +5083,7 @@
                     stopServiceAndUpdateAllowlistManagerLocked(service);
                 }
                 service.setProcess(null, null, 0, null);
-                service.isolatedProc = null;
+                service.isolationHostProc = null;
                 if (mTmpCollectionResults == null) {
                     mTmpCollectionResults = new ArrayList<>();
                 }
@@ -5321,7 +5321,7 @@
                 sr.app.mServices.updateBoundClientUids();
             }
             sr.setProcess(null, null, 0, null);
-            sr.isolatedProc = null;
+            sr.isolationHostProc = null;
             sr.executeNesting = 0;
             synchronized (mAm.mProcessStats.mLock) {
                 sr.forceClearTracker();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f67e732..b1b4c44 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2871,13 +2871,51 @@
         return mode == AppOpsManager.MODE_ALLOWED;
     }
 
-    @Override
-    public int getPackageProcessState(String packageName, String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "getPackageProcessState");
+    /**
+     * Checks whether the calling package is trusted.
+     *
+     * The calling package is trusted if it's from system or the supposed package name matches the
+     * UID making the call.
+     *
+     * @throws SecurityException if the package name and UID don't match.
+     */
+    private void verifyCallingPackage(String callingPackage) {
+        final int callingUid = Binder.getCallingUid();
+        // The caller is System or Shell.
+        if (callingUid == SYSTEM_UID || isCallerShell()) {
+            return;
         }
 
+        // Handle the special UIDs that don't have real package (audioserver, cameraserver, etc).
+        final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid,
+                null /* packageName */);
+        if (resolvedPackage != null && resolvedPackage.equals(callingPackage)) {
+            return;
+        }
+
+        final int claimedUid = getPackageManagerInternal().getPackageUid(callingPackage,
+                0 /* flags */, UserHandle.getUserId(callingUid));
+        if (callingUid == claimedUid) {
+            return;
+        }
+
+        throw new SecurityException(
+                "Claimed calling package " + callingPackage + " does not match the calling UID "
+                        + Binder.getCallingUid());
+    }
+
+    private void enforceUsageStatsPermission(String callingPackage, String func) {
+        verifyCallingPackage(callingPackage);
+        // Since the protection level of PACKAGE_USAGE_STATS has 'appop', apps may grant this
+        // permission via that way. We need to check both app-ops and permission.
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, func);
+        }
+    }
+
+    @Override
+    public int getPackageProcessState(String packageName, String callingPackage) {
+        enforceUsageStatsPermission(callingPackage, "getPackageProcessState");
         final int[] procState = {PROCESS_STATE_NONEXISTENT};
         synchronized (mProcLock) {
             mProcessList.forEachLruProcessesLOSP(false, proc -> {
@@ -6938,11 +6976,7 @@
 
     @Override
     public int getUidProcessState(int uid, String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "getUidProcessState");
-        }
-
+        enforceUsageStatsPermission(callingPackage, "getUidProcessState");
         synchronized (mProcLock) {
             return mProcessList.getUidProcStateLOSP(uid);
         }
@@ -6950,11 +6984,7 @@
 
     @Override
     public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "getUidProcessState");
-        }
-
+        enforceUsageStatsPermission(callingPackage, "getUidProcessCapabilities");
         synchronized (mProcLock) {
             return mProcessList.getUidProcessCapabilityLOSP(uid);
         }
@@ -6963,10 +6993,7 @@
     @Override
     public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "registerUidObserver");
-        }
+        enforceUsageStatsPermission(callingPackage, "registerUidObserver");
         mUidObserverController.register(observer, which, cutpoint, callingPackage,
                 Binder.getCallingUid());
     }
@@ -6978,10 +7005,7 @@
 
     @Override
     public boolean isUidActive(int uid, String callingPackage) {
-        if (!hasUsageStatsPermission(callingPackage)) {
-            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
-                    "isUidActive");
-        }
+        enforceUsageStatsPermission(callingPackage, "isUidActive");
         synchronized (mProcLock) {
             if (isUidActiveLOSP(uid)) {
                 return true;
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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 9ffafe25..0b92954 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -42,6 +42,7 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Binder;
+import android.os.BluetoothBatteryStats;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -2233,6 +2234,7 @@
         pw.println("  --read-daily: read-load last written daily stats.");
         pw.println("  --settings: dump the settings key/values related to batterystats");
         pw.println("  --cpu: dump cpu stats for debugging purpose");
+        pw.println("  --power-profile: dump the power profile constants");
         pw.println("  <package.name>: optional name of package to filter output by.");
         pw.println("  -h: print this help text.");
         pw.println("Battery stats (batterystats) commands:");
@@ -2270,6 +2272,12 @@
         }
     }
 
+    private void dumpPowerProfile(PrintWriter pw) {
+        synchronized (mStats) {
+            mStats.dumpPowerProfileLocked(pw);
+        }
+    }
+
     private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
         i++;
         if (i >= args.length) {
@@ -2412,6 +2420,9 @@
                 } else  if ("--measured-energy".equals(arg)) {
                     dumpMeasuredEnergyStats(pw);
                     return;
+                } else if ("--power-profile".equals(arg)) {
+                    dumpPowerProfile(pw);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
@@ -2617,6 +2628,20 @@
     }
 
     /**
+     * Gets a snapshot of Bluetooth stats
+     * @hide
+     */
+    public BluetoothBatteryStats getBluetoothBatteryStats() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null);
+
+        // Wait for the completion of pending works if there is any
+        awaitCompletion();
+        synchronized (mStats) {
+            return mStats.getBluetoothBatteryStats();
+        }
+    }
+
+    /**
      * Gets a snapshot of the system health for a particular uid.
      */
     @Override
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/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c08cf64..6f22c61 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -962,7 +962,7 @@
         }
 
         opt.setFreezerOverride(false);
-        if (!opt.isFrozen()) {
+        if (pid == 0 || !opt.isFrozen()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 625dd63..df792ee2 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.am;
 
+import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.os.Process.PROC_CHAR;
 import static android.os.Process.PROC_OUT_LONG;
 import static android.os.Process.PROC_PARENS;
@@ -46,6 +47,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
@@ -72,6 +74,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.FileDescriptor;
@@ -177,12 +180,22 @@
                 cpr = mProviderMap.getProviderByName(name, UserHandle.USER_SYSTEM);
                 if (cpr != null) {
                     cpi = cpr.info;
+
                     if (mService.isSingleton(
                             cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags)
                                 && mService.isValidSingletonCall(
                                         r == null ? callingUid : r.uid, cpi.applicationInfo.uid)) {
                         userId = UserHandle.USER_SYSTEM;
                         checkCrossUser = false;
+                    } else if (isAuthorityRedirectedForCloneProfile(name)) {
+                        UserManagerInternal umInternal = LocalServices.getService(
+                                UserManagerInternal.class);
+                        UserInfo userInfo = umInternal.getUserInfo(userId);
+
+                        if (userInfo != null && userInfo.isCloneProfile()) {
+                            userId = umInternal.getProfileParentId(userId);
+                            checkCrossUser = false;
+                        }
                     } else {
                         cpr = null;
                         cpi = null;
@@ -1026,12 +1039,23 @@
      * at the given authority and user.
      */
     String checkContentProviderAccess(String authority, int userId) {
+        boolean checkUser = true;
         if (userId == UserHandle.USER_ALL) {
             mService.mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, TAG);
             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,
@@ -1060,7 +1084,7 @@
         }
 
         return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
-                userId, true, appName);
+                userId, checkUser, appName);
     }
 
     int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b123496..bdfd02e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -598,7 +598,7 @@
             for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
                 ConnectionRecord cr = psr.getConnectionAt(i);
                 ProcessRecord service = (cr.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
-                        ? cr.binding.service.isolatedProc : cr.binding.service.app;
+                        ? cr.binding.service.isolationHostProc : cr.binding.service.app;
                 if (service == null || service == pr) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index c830554..be187e2 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -513,7 +513,7 @@
             }
         }
         processInfo = procInfo;
-        isolated = _info.uid != _uid;
+        isolated = Process.isIsolated(_uid);
         appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID
                 && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID);
         uid = _uid;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 9b32e61..24e7ba4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -102,7 +102,8 @@
                             // IBinder -> ConnectionRecord of all bound clients
 
     ProcessRecord app;      // where this service is running or null.
-    ProcessRecord isolatedProc; // keep track of isolated process, if requested
+    ProcessRecord isolationHostProc; // process which we've started for this service (used for
+                                     // isolated and supplemental processes)
     ServiceState tracker; // tracking service execution, may be null
     ServiceState restartTracker; // tracking service restart
     boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
@@ -352,8 +353,8 @@
         if (app != null) {
             app.dumpDebug(proto, ServiceRecordProto.APP);
         }
-        if (isolatedProc != null) {
-            isolatedProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC);
+        if (isolationHostProc != null) {
+            isolationHostProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC);
         }
         proto.write(ServiceRecordProto.WHITELIST_MANAGER, allowlistManager);
         proto.write(ServiceRecordProto.DELAYED, delayed);
@@ -455,8 +456,8 @@
             pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir);
         }
         pw.print(prefix); pw.print("app="); pw.println(app);
-        if (isolatedProc != null) {
-            pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc);
+        if (isolationHostProc != null) {
+            pw.print(prefix); pw.print("isolationHostProc="); pw.println(isolationHostProc);
         }
         if (allowlistManager) {
             pw.print(prefix); pw.print("allowlistManager="); pw.println(allowlistManager);
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
new file mode 100644
index 0000000..6982513
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
@@ -0,0 +1,294 @@
+/*
+ * 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.ambientcontext;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.app.BroadcastOptions;
+import android.app.PendingIntent;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.app.ambientcontext.AmbientContextManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.service.ambientcontext.AmbientContextDetectionService;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Per-user manager service for {@link AmbientContextEvent}s.
+ */
+final class AmbientContextManagerPerUserService extends
+        AbstractPerUserSystemService<AmbientContextManagerPerUserService,
+                AmbientContextManagerService> {
+    private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName();
+
+    @Nullable
+    @VisibleForTesting
+    RemoteAmbientContextDetectionService mRemoteService;
+
+    private ComponentName mComponentName;
+    private Context mContext;
+    private Set<PendingIntent> mExistingPendingIntents;
+
+    AmbientContextManagerPerUserService(
+            @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
+        super(master, lock, userId);
+        mContext = master.getContext();
+        mExistingPendingIntents = new HashSet<>();
+    }
+
+    void destroyLocked() {
+        if (isVerbose()) {
+            Slog.v(TAG, "destroyLocked()");
+        }
+
+        Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
+        if (mRemoteService != null) {
+            synchronized (mLock) {
+                mRemoteService.unbind();
+                mRemoteService = null;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void ensureRemoteServiceInitiated() {
+        if (mRemoteService == null) {
+            mRemoteService = new RemoteAmbientContextDetectionService(
+                    getContext(), mComponentName, getUserId());
+        }
+    }
+
+    /**
+     * get the currently bound component name.
+     */
+    @VisibleForTesting
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+
+    /**
+     * Resolves and sets up the service if it had not been done yet. Returns true if the service
+     * is available.
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    boolean setUpServiceIfNeeded() {
+        if (mComponentName == null) {
+            mComponentName = updateServiceInfoLocked();
+        }
+        return mComponentName != null;
+    }
+
+    @Override
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        ServiceInfo serviceInfo;
+        try {
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    0, mUserId);
+            if (serviceInfo != null) {
+                final String permission = serviceInfo.permission;
+                if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals(
+                        permission)) {
+                    throw new SecurityException(String.format(
+                            "Service %s requires %s permission. Found %s permission",
+                            serviceInfo.getComponentName(),
+                            Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE,
+                            serviceInfo.permission));
+                }
+            }
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+        return serviceInfo;
+    }
+
+    @Override
+    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+        synchronized (super.mLock) {
+            super.dumpLocked(prefix, pw);
+        }
+        if (mRemoteService != null) {
+            mRemoteService.dump("", new IndentingPrintWriter(pw, "  "));
+        }
+    }
+
+    /**
+     * Handles client registering as an observer. Only one registration is supported per app
+     * package. A new registration from the same package will overwrite the previous registration.
+     */
+    public void onRegisterObserver(AmbientContextEventRequest request,
+            PendingIntent pendingIntent) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Service is not available at this moment.");
+                sendStatusUpdateIntent(
+                        pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+
+            // Remove any existing intent and unregister for this package before adding a new one.
+            String callingPackage = pendingIntent.getCreatorPackage();
+            PendingIntent duplicatePendingIntent = findExistingRequestByPackage(callingPackage);
+            if (duplicatePendingIntent != null) {
+                Slog.d(TAG, "Unregister duplicate request from " + callingPackage);
+                onUnregisterObserver(callingPackage);
+                mExistingPendingIntents.remove(duplicatePendingIntent);
+            }
+
+            // Register new package and add request to mExistingRequests
+            startDetection(request, callingPackage, createRemoteCallback());
+            mExistingPendingIntents.add(pendingIntent);
+        }
+    }
+
+    @VisibleForTesting
+    void startDetection(AmbientContextEventRequest request, String callingPackage,
+            RemoteCallback callback) {
+        Slog.d(TAG, "Requested detection of " + request.getEventTypes());
+        synchronized (mLock) {
+            ensureRemoteServiceInitiated();
+            mRemoteService.startDetection(request, callingPackage, callback);
+        }
+    }
+
+    /**
+     * Sends an intent with a status code and empty events.
+     */
+    void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) {
+        AmbientContextEventResponse response = new AmbientContextEventResponse.Builder()
+                .setStatusCode(statusCode)
+                .build();
+        sendResponseIntent(pendingIntent, response);
+    }
+
+    /**
+     * Unregisters the client from all previously registered events by removing from the
+     * mExistingRequests map, and unregister events from the service if those events are not
+     * requested by other apps.
+     */
+    public void onUnregisterObserver(String callingPackage) {
+        synchronized (mLock) {
+            PendingIntent pendingIntent = findExistingRequestByPackage(callingPackage);
+            if (pendingIntent == null) {
+                Slog.d(TAG, "No registration found for " + callingPackage);
+                return;
+            }
+
+            // Remove from existing requests
+            mExistingPendingIntents.remove(pendingIntent);
+            stopDetection(pendingIntent.getCreatorPackage());
+        }
+    }
+
+    @VisibleForTesting
+    void stopDetection(String packageName) {
+        Slog.d(TAG, "Stop detection for " + packageName);
+        synchronized (mLock) {
+            ensureRemoteServiceInitiated();
+            mRemoteService.stopDetection(packageName);
+        }
+    }
+
+    @Nullable
+    private PendingIntent findExistingRequestByPackage(String callingPackage) {
+        for (PendingIntent pendingIntent : mExistingPendingIntents) {
+            if (pendingIntent.getCreatorPackage().equals(callingPackage)) {
+                return pendingIntent;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sends out the Intent to the client after the event is detected.
+     *
+     * @param pendingIntent Client's PendingIntent for callback
+     * @param response Response with status code and detection result
+     */
+    private void sendResponseIntent(PendingIntent pendingIntent,
+            AmbientContextEventResponse response) {
+        Intent intent = new Intent();
+        intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response);
+        // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
+        // the PendingIntent as a backdoor to do this.
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        try {
+            pendingIntent.send(getContext(), 0, intent, null, null, null,
+                    options.toBundle());
+            Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
+                    + response);
+        } catch (PendingIntent.CanceledException e) {
+            Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
+        }
+    }
+
+    @NonNull
+    private RemoteCallback createRemoteCallback() {
+        return new RemoteCallback(result -> {
+            AmbientContextEventResponse response = (AmbientContextEventResponse) result.get(
+                            AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>();
+                for (PendingIntent pendingIntent : mExistingPendingIntents) {
+                    // Send PendingIntent if a requesting package matches the response packages.
+                    if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) {
+                        sendResponseIntent(pendingIntent, response);
+
+                        int statusCode = response.getStatusCode();
+                        if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) {
+                            pendingIntentForFailedRequests.add(pendingIntent);
+                        }
+                        Slog.i(TAG, "Got response of " + response.getEvents() + " for "
+                                + pendingIntent.getCreatorPackage() + ". Status: " + statusCode);
+                    }
+                }
+
+                // Removes the failed requests from the existing requests.
+                for (PendingIntent pendingIntent : pendingIntentForFailedRequests) {
+                    mExistingPendingIntents.remove(pendingIntent);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
new file mode 100644
index 0000000..33905f2
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -0,0 +1,220 @@
+/*
+ * 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.ambientcontext;
+
+import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.PendingIntent;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.app.ambientcontext.IAmbientContextEventObserver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.RemoteCallback;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * System service for managing {@link AmbientContextEvent}s.
+ */
+public class AmbientContextManagerService extends
+        AbstractMasterSystemService<AmbientContextManagerService,
+                AmbientContextManagerPerUserService> {
+    private static final String TAG = AmbientContextManagerService.class.getSimpleName();
+    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
+    private final Context mContext;
+    boolean mIsServiceEnabled;
+
+    public AmbientContextManagerService(Context context) {
+        super(context,
+                new FrameworkResourcesServiceNameResolver(
+                        context,
+                        R.string.config_defaultAmbientContextDetectionService),
+                        /*disallowProperty=*/null,
+                PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+                        | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
+        mContext = context;
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver());
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            DeviceConfig.addOnPropertiesChangedListener(
+                    NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
+                    getContext().getMainExecutor(),
+                    (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+            mIsServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+        }
+    }
+
+    private void onDeviceConfigChange(@NonNull Set<String> keys) {
+        if (keys.contains(KEY_SERVICE_ENABLED)) {
+            mIsServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+        }
+    }
+
+    @Override
+    protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId,
+            boolean disabled) {
+        return new AmbientContextManagerPerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    protected void onServiceRemoved(
+            AmbientContextManagerPerUserService service, @UserIdInt int userId) {
+        service.destroyLocked();
+    }
+
+    /** Returns {@code true} if the detection service is configured on this device. */
+    public static boolean isDetectionServiceConfigured() {
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        final String[] packageNames = pmi.getKnownPackageNames(
+                PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION, UserHandle.USER_SYSTEM);
+        boolean isServiceConfigured = (packageNames.length != 0);
+        Slog.i(TAG, "Detection service configured: " + isServiceConfigured);
+        return isServiceConfigured;
+    }
+
+    /**
+     * Send request to the remote AmbientContextDetectionService impl to start detecting the
+     * specified events. Intended for use by shell command for testing.
+     * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
+     */
+    void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request,
+            String packageName, RemoteCallback callback) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+        synchronized (mLock) {
+            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.startDetection(request, packageName, callback);
+            } else {
+                Slog.i(TAG, "service not available for user_id: " + userId);
+            }
+        }
+    }
+
+    /**
+     * Send request to the remote AmbientContextDetectionService impl to stop detecting the
+     * specified events. Intended for use by shell command for testing.
+     * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
+     */
+    void stopAmbientContextEvent(@UserIdInt int userId, String packageName) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+        synchronized (mLock) {
+            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.stopDetection(packageName);
+            } else {
+                Slog.i(TAG, "service not available for user_id: " + userId);
+            }
+        }
+    }
+
+    /**
+     * Returns the AmbientContextManagerPerUserService component for this user.
+     */
+    public ComponentName getComponentName(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                return service.getComponentName();
+            }
+        }
+        return null;
+    }
+
+    private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub {
+        final AmbientContextManagerPerUserService mService = getServiceForUserLocked(
+                UserHandle.getCallingUserId());
+
+        @Override
+        public void registerObserver(
+                AmbientContextEventRequest request, PendingIntent pendingIntent) {
+            Objects.requireNonNull(request);
+            Objects.requireNonNull(pendingIntent);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                mService.sendStatusUpdateIntent(pendingIntent,
+                        AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            mService.onRegisterObserver(request, pendingIntent);
+        }
+
+        @Override
+        public void unregisterObserver(String callingPackage) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            mService.onUnregisterObserver(callingPackage);
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+                return;
+            }
+            synchronized (mLock) {
+                dumpLocked("", pw);
+            }
+        }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            new AmbientContextShellCommand(AmbientContextManagerService.this).exec(
+                    this, in, out, err, args, callback, resultReceiver);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
new file mode 100644
index 0000000..b5cd985
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ambientcontext;
+
+import static java.lang.System.out;
+
+import android.annotation.NonNull;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.ambientcontext.AmbientContextEventResponse;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.RemoteCallback;
+import android.os.ShellCommand;
+import android.service.ambientcontext.AmbientContextDetectionService;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command for {@link AmbientContextManagerService}.
+ */
+final class AmbientContextShellCommand extends ShellCommand {
+
+    @NonNull
+    private final AmbientContextManagerService mService;
+
+    AmbientContextShellCommand(@NonNull AmbientContextManagerService service) {
+        mService = service;
+    }
+
+    /** Callbacks for AmbientContextEventService results used internally for testing. */
+    static class TestableCallbackInternal {
+        private AmbientContextEventResponse mLastResponse;
+
+        public AmbientContextEventResponse getLastResponse() {
+            return mLastResponse;
+        }
+
+        @NonNull
+        private RemoteCallback createRemoteCallback() {
+            return new RemoteCallback(result -> {
+                AmbientContextEventResponse response =
+                        (AmbientContextEventResponse) result.get(
+                                AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mLastResponse = response;
+                    out.println("Response available: " + response);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            });
+        }
+    }
+
+    static final TestableCallbackInternal sTestableCallbackInternal =
+            new TestableCallbackInternal();
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        switch (cmd) {
+            case "start-detection":
+                return runStartDetection();
+            case "stop-detection":
+                return runStopDetection();
+            case "get-last-status-code":
+                return getLastStatusCode();
+            case "get-bound-package":
+                return getBoundPackageName();
+            case "set-temporary-service":
+                return setTemporaryService();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    private int runStartDetection() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
+                .addEventType(AmbientContextEvent.EVENT_COUGH)
+                .addEventType(AmbientContextEvent.EVENT_SNORE)
+                .build();
+
+        mService.startAmbientContextEvent(userId, request, packageName,
+                sTestableCallbackInternal.createRemoteCallback());
+        return 0;
+    }
+
+    private int runStopDetection() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        mService.stopAmbientContextEvent(userId, packageName);
+        return 0;
+    }
+
+    private int getLastStatusCode() {
+        AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse();
+        if (lastResponse == null) {
+            return -1;
+        }
+        return lastResponse.getStatusCode();
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("AmbientContextEvent commands: ");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection.");
+        pw.println("  stop-detection USER_ID: Stops AmbientContextEvent detection.");
+        pw.println("  get-last-status-code: Prints the latest request status code.");
+        pw.println("  get-bound-package USER_ID:"
+                + "     Print the bound package that implements the service.");
+        pw.println("  set-temporary-service USER_ID [COMPONENT_NAME DURATION]");
+        pw.println("    Temporarily (for DURATION ms) changes the service implementation.");
+        pw.println("    To reset, call with just the USER_ID argument.");
+    }
+
+    private int getBoundPackageName() {
+        final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final ComponentName componentName = mService.getComponentName(userId);
+        out.println(componentName == null ? "" : componentName.getPackageName());
+        return 0;
+    }
+
+    private int setTemporaryService() {
+        final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String serviceName = getNextArg();
+        if (serviceName == null) {
+            mService.resetTemporaryService(userId);
+            out.println("AmbientContextDetectionService temporary reset. ");
+            return 0;
+        }
+
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryService(userId, serviceName, duration);
+        out.println("AmbientContextDetectionService temporarily set to " + serviceName
+                + " for " + duration + "ms");
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/ambientcontext/OWNERS b/services/core/java/com/android/server/ambientcontext/OWNERS
new file mode 100644
index 0000000..a863297
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/OWNERS
@@ -0,0 +1,3 @@
+enxun@google.com
+kxchen@google.com
+tgadh@google.com
diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
new file mode 100644
index 0000000..5cc29b3
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
@@ -0,0 +1,74 @@
+/*
+ * 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.ambientcontext;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.annotation.NonNull;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteCallback;
+import android.service.ambientcontext.AmbientContextDetectionService;
+import android.service.ambientcontext.IAmbientContextDetectionService;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+/** Manages the connection to the remote service. */
+final class RemoteAmbientContextDetectionService
+        extends ServiceConnector.Impl<IAmbientContextDetectionService> {
+    private static final String TAG =
+            RemoteAmbientContextDetectionService.class.getSimpleName();
+
+    RemoteAmbientContextDetectionService(Context context, ComponentName serviceName,
+            int userId) {
+        super(context, new Intent(
+                AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName),
+                BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+                IAmbientContextDetectionService.Stub::asInterface);
+
+        // Bind right away
+        connect();
+    }
+
+    /**
+     * Asks the implementation to start detection.
+     *
+     * @param request The request with events to detect, and optional detection options.
+     * @param packageName The app package that requested the detection
+     * @param callback callback for detection results
+     */
+    public void startDetection(
+            @NonNull AmbientContextEventRequest request, String packageName,
+            RemoteCallback callback) {
+        Slog.i(TAG, "Start detection for " + request.getEventTypes());
+        post(service -> service.startDetection(request, packageName, callback));
+    }
+
+    /**
+     * Asks the implementation to stop detection.
+     *
+     * @param packageName stop detection for the given package
+     */
+    public void stopDetection(String packageName) {
+        Slog.i(TAG, "Stop detection for " + packageName);
+        post(service -> service.stopDetection(packageName));
+    }
+}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 53f651b..417ebcd 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -43,6 +43,7 @@
 import android.app.ActivityManager;
 import android.app.GameManager;
 import android.app.GameManager.GameMode;
+import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameManagerService;
 import android.app.compat.PackageOverride;
@@ -694,6 +695,32 @@
         }
     }
 
+    private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
+        GamePackageConfiguration config = null;
+        synchronized (mOverrideConfigLock) {
+            config = mOverrideConfigs.get(packageName);
+        }
+        if (config == null) {
+            synchronized (mDeviceConfigLock) {
+                config = mConfigs.get(packageName);
+            }
+        }
+        if (config == null) {
+            return new int[]{};
+        }
+        return config.getAvailableGameModes();
+    }
+
+    private boolean isPackageGame(String packageName, @UserIdInt int userId) {
+        try {
+            final ApplicationInfo applicationInfo = mPackageManager
+                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+            return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     /**
      * Get an array of game modes available for a given package.
      * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
@@ -702,19 +729,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
     public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-        GamePackageConfiguration config = null;
-        synchronized (mOverrideConfigLock) {
-            config = mOverrideConfigs.get(packageName);
-        }
-        if (config == null) {
-        synchronized (mDeviceConfigLock) {
-                config = mConfigs.get(packageName);
-            }
-        }
-        if (config == null) {
-            return new int[]{GameManager.GAME_MODE_UNSUPPORTED};
-        }
-        return config.getAvailableGameModes();
+        return getAvailableGameModesUnchecked(packageName);
     }
 
     private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) {
@@ -735,28 +750,22 @@
      * {@link android.Manifest.permission#MANAGE_GAME_MODE}.
      */
     @Override
-    public @GameMode int getGameMode(String packageName, int userId)
+    public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId)
             throws SecurityException {
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getGameMode",
                 "com.android.server.app.GameManagerService");
 
         // Restrict to games only.
-        try {
-            final ApplicationInfo applicationInfo = mPackageManager
-                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
-            if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
-                // The game mode for applications that are not identified as game is always
-                // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
-                return GameManager.GAME_MODE_UNSUPPORTED;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
+        if (!isPackageGame(packageName, userId)) {
+            // The game mode for applications that are not identified as game is always
+            // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)}
             return GameManager.GAME_MODE_UNSUPPORTED;
         }
 
         // This function handles two types of queries:
-        // 1.) A normal, non-privileged app querying its own Game Mode.
-        // 2.) A privileged system service querying the Game Mode of another package.
+        // 1) A normal, non-privileged app querying its own Game Mode.
+        // 2) A privileged system service querying the Game Mode of another package.
         // The least privileged case is a normal app performing a query, so check that first and
         // return a value if the package name is valid. Next, check if the caller has the necessary
         // permission and return a value. Do this check last, since it can throw an exception.
@@ -769,14 +778,32 @@
         return getGameModeFromSettings(packageName, userId);
     }
 
-    private boolean isPackageGame(String packageName, int userId) {
-        try {
-            final ApplicationInfo applicationInfo = mPackageManager
-                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
-            return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
+    /**
+     * Get the GameModeInfo for the package name.
+     * Verifies that the calling process is for the matching package UID or has
+     * {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game,
+     * null is always returned.
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+    @Nullable
+    public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) {
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getGameModeInfo",
+                "com.android.server.app.GameManagerService");
+
+        // Check the caller has the necessary permission.
+        checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+
+        // Restrict to games only.
+        if (!isPackageGame(packageName, userId)) {
+            return null;
         }
+
+        final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId);
+        final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName);
+
+        return new GameModeInfo(activeGameMode, availableGameModes);
     }
 
     /**
@@ -911,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 {
@@ -1003,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/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
index d5ac03a..b4c43f6 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java
@@ -20,6 +20,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
+import android.os.ServiceManager;
 import android.service.games.GameService;
 import android.service.games.GameSessionService;
 import android.service.games.IGameService;
@@ -27,6 +28,9 @@
 
 import com.android.internal.infra.ServiceConnector;
 import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerService;
 
 final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory {
     private final Context mContext;
@@ -44,6 +48,8 @@
                 BackgroundThread.getExecutor(),
                 new GameClassifierImpl(mContext.getPackageManager()),
                 ActivityTaskManager.getService(),
+                (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE),
+                LocalServices.getService(WindowManagerInternal.class),
                 new GameServiceConnector(mContext, gameServiceProviderConfiguration),
                 new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration));
     }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index cc060e9..43c9839 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -17,24 +17,37 @@
 package com.android.server.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
-import android.os.IBinder;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.games.CreateGameSessionRequest;
+import android.service.games.CreateGameSessionResult;
+import android.service.games.GameScreenshotResult;
+import android.service.games.GameSessionViewHostConfiguration;
 import android.service.games.GameStartedEvent;
 import android.service.games.IGameService;
 import android.service.games.IGameServiceController;
 import android.service.games.IGameSession;
+import android.service.games.IGameSessionController;
 import android.service.games.IGameSessionService;
 import android.util.Slog;
+import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerService;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -62,6 +75,19 @@
                 GameServiceProviderInstanceImpl.this.onTaskRemoved(taskId);
             });
         }
+
+        @Override
+        public void onTaskFocusChanged(int taskId, boolean focused) {
+            mBackgroundExecutor.execute(() -> {
+                GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused);
+            });
+        }
+
+        // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider
+        // to only when the associated task is running. Right now it is possible for a task to
+        // move into the background and for all associated processes to die and for the Game Session
+        // provider's GameSessionService to continue to be running. Ideally we could unbind the
+        // service when this happens.
     };
 
     private final IGameServiceController mGameServiceController =
@@ -74,11 +100,25 @@
                 }
             };
 
+    private final IGameSessionController mGameSessionController =
+            new IGameSessionController.Stub() {
+                @Override
+                public void takeScreenshot(int taskId,
+                        @NonNull AndroidFuture gameScreenshotResultFuture) {
+                    mBackgroundExecutor.execute(() -> {
+                        GameServiceProviderInstanceImpl.this.takeScreenshot(taskId,
+                                gameScreenshotResultFuture);
+                    });
+                }
+            };
+
     private final Object mLock = new Object();
     private final UserHandle mUserHandle;
     private final Executor mBackgroundExecutor;
     private final GameClassifier mGameClassifier;
     private final IActivityTaskManager mActivityTaskManager;
+    private final WindowManagerService mWindowManagerService;
+    private final WindowManagerInternal mWindowManagerInternal;
     private final ServiceConnector<IGameService> mGameServiceConnector;
     private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector;
 
@@ -89,16 +129,20 @@
     private volatile boolean mIsRunning;
 
     GameServiceProviderInstanceImpl(
-            UserHandle userHandle,
+            @NonNull UserHandle userHandle,
             @NonNull Executor backgroundExecutor,
             @NonNull GameClassifier gameClassifier,
             @NonNull IActivityTaskManager activityTaskManager,
+            @NonNull WindowManagerService windowManagerService,
+            @NonNull WindowManagerInternal windowManagerInternal,
             @NonNull ServiceConnector<IGameService> gameServiceConnector,
             @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) {
         mUserHandle = userHandle;
         mBackgroundExecutor = backgroundExecutor;
         mGameClassifier = gameClassifier;
         mActivityTaskManager = activityTaskManager;
+        mWindowManagerService = windowManagerService;
+        mWindowManagerInternal = windowManagerInternal;
         mGameServiceConnector = gameServiceConnector;
         mGameSessionServiceConnector = gameSessionServiceConnector;
     }
@@ -151,16 +195,7 @@
         }
 
         for (GameSessionRecord gameSessionRecord : mGameSessions.values()) {
-            IGameSession gameSession = gameSessionRecord.getGameSession();
-            if (gameSession == null) {
-                continue;
-            }
-
-            try {
-                gameSession.destroy();
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
-            }
+            destroyGameSessionFromRecord(gameSessionRecord);
         }
         mGameSessions.clear();
 
@@ -185,31 +220,55 @@
         }
     }
 
+    private void onTaskFocusChanged(int taskId, boolean focused) {
+        synchronized (mLock) {
+            onTaskFocusChangedLocked(taskId, focused);
+        }
+    }
+
     @GuardedBy("mLock")
-    private void gameTaskStartedLocked(int sessionId, @NonNull ComponentName componentName) {
+    private void onTaskFocusChangedLocked(int taskId, boolean focused) {
         if (DEBUG) {
-            Slog.i(TAG, "gameStartedLocked() id: " + sessionId + " component: " + componentName);
+            Slog.d(TAG, "onTaskFocusChangedLocked() id: " + taskId + " focused: " + focused);
+        }
+
+        final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId);
+        if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) {
+            return;
+        }
+
+        try {
+            gameSessionRecord.getGameSession().onTaskFocusChanged(focused);
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed to notify session of task focus change: " + gameSessionRecord);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) {
+        if (DEBUG) {
+            Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName);
         }
 
         if (!mIsRunning) {
             return;
         }
 
-        GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId);
+        GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId);
         if (existingGameSessionRecord != null) {
-            Slog.w(TAG, "Existing game session found for task (id: " + sessionId
+            Slog.w(TAG, "Existing game session found for task (id: " + taskId
                     + ") creation. Ignoring.");
             return;
         }
 
         GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest(
-                sessionId, componentName);
-        mGameSessions.put(sessionId, gameSessionRecord);
+                taskId, componentName);
+        mGameSessions.put(taskId, gameSessionRecord);
 
         AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post(
                 gameService -> {
                     gameService.gameStarted(
-                            new GameStartedEvent(sessionId, componentName.getPackageName()));
+                            new GameStartedEvent(taskId, componentName.getPackageName()));
                 });
     }
 
@@ -220,7 +279,7 @@
                 return;
             }
 
-            destroyGameSessionIfNecessaryLocked(taskId);
+            removeAndDestroyGameSessionIfNecessaryLocked(taskId);
         }
     }
 
@@ -231,112 +290,175 @@
     }
 
     @GuardedBy("mLock")
-    private void createGameSessionLocked(int sessionId) {
+    private void createGameSessionLocked(int taskId) {
         if (DEBUG) {
-            Slog.i(TAG, "createGameSessionLocked() id: " + sessionId);
+            Slog.i(TAG, "createGameSessionLocked() id: " + taskId);
         }
 
         if (!mIsRunning) {
             return;
         }
 
-        GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId);
+        GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId);
         if (existingGameSessionRecord == null) {
-            Slog.w(TAG, "No existing game session record found for task (id: " + sessionId
+            Slog.w(TAG, "No existing game session record found for task (id: " + taskId
                     + ") creation. Ignoring.");
             return;
         }
         if (!existingGameSessionRecord.isAwaitingGameSessionRequest()) {
-            Slog.w(TAG, "Existing game session for task (id: " + sessionId
+            Slog.w(TAG, "Existing game session for task (id: " + taskId
                     + ") is not awaiting game session request. Ignoring.");
             return;
         }
-        mGameSessions.put(sessionId, existingGameSessionRecord.withGameSessionRequested());
 
-        ComponentName componentName = existingGameSessionRecord.getComponentName();
+        GameSessionViewHostConfiguration gameSessionViewHostConfiguration =
+                createViewHostConfigurationForTask(taskId);
+        if (gameSessionViewHostConfiguration == null) {
+            Slog.w(TAG, "Failed to create view host configuration for task (id" + taskId
+                    + ") creation. Ignoring.");
+            return;
+        }
 
-        // TODO(b/207035150): Allow the game service provider to determine if a game session
-        //  should be created. For now we will assume all games should have a session.
-        AndroidFuture<IBinder> gameSessionFuture = new AndroidFuture<IBinder>()
-                .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
-                .whenCompleteAsync((gameSessionIBinder, exception) -> {
-                    IGameSession gameSession = IGameSession.Stub.asInterface(gameSessionIBinder);
-                    if (exception != null || gameSession == null) {
-                        Slog.w(TAG, "Failed to create GameSession: " + existingGameSessionRecord,
-                                exception);
-                        synchronized (mLock) {
-                            destroyGameSessionIfNecessaryLocked(sessionId);
-                        }
-                        return;
-                    }
+        if (DEBUG) {
+            Slog.d(TAG, "Determined initial view host configuration for task (id: " + taskId + "): "
+                    + gameSessionViewHostConfiguration);
+        }
 
-                    synchronized (mLock) {
-                        attachGameSessionLocked(sessionId, gameSession);
-                    }
-                }, mBackgroundExecutor);
+        mGameSessions.put(taskId, existingGameSessionRecord.withGameSessionRequested());
+
+        AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture =
+                new AndroidFuture<CreateGameSessionResult>()
+                        .orTimeout(CREATE_GAME_SESSION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+                        .whenCompleteAsync((createGameSessionResult, exception) -> {
+                            if (exception != null || createGameSessionResult == null) {
+                                Slog.w(TAG, "Failed to create GameSession: "
+                                                + existingGameSessionRecord,
+                                        exception);
+                                synchronized (mLock) {
+                                    removeAndDestroyGameSessionIfNecessaryLocked(taskId);
+                                }
+                                return;
+                            }
+
+                            synchronized (mLock) {
+                                attachGameSessionLocked(taskId, createGameSessionResult);
+                            }
+
+                            // The TaskStackListener may have made its task focused call for the
+                            // game session's task before the game session was created, so check if
+                            // the task is already focused so that the game session can be notified.
+                            setGameSessionFocusedIfNecessary(taskId,
+                                    createGameSessionResult.getGameSession());
+                        }, mBackgroundExecutor);
 
         AndroidFuture<Void> unusedPostCreateGameSessionFuture =
                 mGameSessionServiceConnector.post(gameService -> {
                     CreateGameSessionRequest createGameSessionRequest =
-                            new CreateGameSessionRequest(sessionId, componentName.getPackageName());
-                    gameService.create(createGameSessionRequest, gameSessionFuture);
+                            new CreateGameSessionRequest(
+                                    taskId,
+                                    existingGameSessionRecord.getComponentName().getPackageName());
+                    gameService.create(
+                            mGameSessionController,
+                            createGameSessionRequest,
+                            gameSessionViewHostConfiguration,
+                            createGameSessionResultFuture);
                 });
     }
 
-    @GuardedBy("mLock")
-    private void attachGameSessionLocked(int sessionId, @NonNull IGameSession gameSession) {
-        if (DEBUG) {
-            Slog.i(TAG, "attachGameSession() id: " + sessionId);
-        }
-
-        GameSessionRecord gameSessionRecord = mGameSessions.get(sessionId);
-        boolean isValidAttachRequest = true;
-        if (gameSessionRecord == null) {
-            Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId);
-            isValidAttachRequest = false;
-        }
-        if (gameSessionRecord != null && !gameSessionRecord.isGameSessionRequested()) {
-            Slog.w(TAG,
-                    "Game session not requested for existing game session record. Destroying id: "
-                            + sessionId);
-            isValidAttachRequest = false;
-        }
-
-        if (!isValidAttachRequest) {
-            try {
-                gameSession.destroy();
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
+    private void setGameSessionFocusedIfNecessary(int taskId, IGameSession gameSession) {
+        try {
+            final ActivityTaskManager.RootTaskInfo rootTaskInfo =
+                    mActivityTaskManager.getFocusedRootTaskInfo();
+            if (rootTaskInfo != null && rootTaskInfo.taskId == taskId) {
+                gameSession.onTaskFocusChanged(true);
             }
-            return;
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed to set task focused for ID: " + taskId);
         }
-
-        mGameSessions.put(sessionId, gameSessionRecord.withGameSession(gameSession));
     }
 
     @GuardedBy("mLock")
-    private void destroyGameSessionIfNecessaryLocked(int sessionId) {
-        // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider
-        // to only when the associated task is running. Right now it is possible for a task to
-        // move into the background and for all associated processes to die and for the Game Session
-        // provider's GameSessionService to continue to be running. Ideally we could unbind the
-        // service when this happens.
+    private void attachGameSessionLocked(
+            int taskId,
+            @NonNull CreateGameSessionResult createGameSessionResult) {
         if (DEBUG) {
-            Slog.i(TAG, "destroyGameSession() id: " + sessionId);
+            Slog.d(TAG, "attachGameSession() id: " + taskId);
         }
 
-        GameSessionRecord gameSessionRecord = mGameSessions.remove(sessionId);
+        GameSessionRecord gameSessionRecord = mGameSessions.get(taskId);
+
+        if (gameSessionRecord == null) {
+            Slog.w(TAG, "No associated game session record. Destroying id: " + taskId);
+            destroyGameSessionDuringAttach(taskId, createGameSessionResult);
+            return;
+        }
+
+        if (!gameSessionRecord.isGameSessionRequested()) {
+            destroyGameSessionDuringAttach(taskId, createGameSessionResult);
+            return;
+        }
+
+        try {
+            mWindowManagerInternal.addTaskOverlay(
+                    taskId,
+                    createGameSessionResult.getSurfacePackage());
+        } catch (IllegalArgumentException ex) {
+            Slog.w(TAG, "Failed to add task overlay. Destroying id: " + taskId);
+            destroyGameSessionDuringAttach(taskId, createGameSessionResult);
+            return;
+        }
+
+        mGameSessions.put(taskId,
+                gameSessionRecord.withGameSession(
+                        createGameSessionResult.getGameSession(),
+                        createGameSessionResult.getSurfacePackage()));
+    }
+
+    private void destroyGameSessionDuringAttach(
+            int taskId,
+            CreateGameSessionResult createGameSessionResult) {
+        try {
+            createGameSessionResult.getGameSession().onDestroyed();
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed to destroy session: " + taskId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void removeAndDestroyGameSessionIfNecessaryLocked(int taskId) {
+        if (DEBUG) {
+            Slog.d(TAG, "destroyGameSession() id: " + taskId);
+        }
+
+        GameSessionRecord gameSessionRecord = mGameSessions.remove(taskId);
         if (gameSessionRecord == null) {
             if (DEBUG) {
-                Slog.w(TAG, "No game session found for id: " + sessionId);
+                Slog.w(TAG, "No game session found for id: " + taskId);
             }
             return;
         }
+        destroyGameSessionFromRecord(gameSessionRecord);
+    }
+
+    private void destroyGameSessionFromRecord(@NonNull GameSessionRecord gameSessionRecord) {
+        SurfacePackage surfacePackage = gameSessionRecord.getSurfacePackage();
+        if (surfacePackage != null) {
+            try {
+                mWindowManagerInternal.removeTaskOverlay(
+                        gameSessionRecord.getTaskId(),
+                        surfacePackage);
+            } catch (IllegalArgumentException ex) {
+                Slog.i(TAG,
+                        "Failed to remove task overlay. This is expected if the task is already "
+                                + "destroyed: "
+                                + gameSessionRecord);
+            }
+        }
 
         IGameSession gameSession = gameSessionRecord.getGameSession();
         if (gameSession != null) {
             try {
-                gameSession.destroy();
+                gameSession.onDestroyed();
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex);
             }
@@ -344,7 +466,7 @@
 
         if (mGameSessions.isEmpty()) {
             if (DEBUG) {
-                Slog.i(TAG, "No active game sessions. Disconnecting GameSessionService");
+                Slog.d(TAG, "No active game sessions. Disconnecting GameSessionService");
             }
 
             if (mGameSessionServiceConnector != null) {
@@ -352,4 +474,62 @@
             }
         }
     }
+
+    @Nullable
+    private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) {
+        RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId);
+        if (runningTaskInfo == null) {
+            return null;
+        }
+
+        Rect bounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
+        return new GameSessionViewHostConfiguration(
+                runningTaskInfo.displayId,
+                bounds.width(),
+                bounds.height());
+    }
+
+    @Nullable
+    private RunningTaskInfo getRunningTaskInfoForTask(int taskId) {
+        List<RunningTaskInfo> runningTaskInfos;
+        try {
+            runningTaskInfos = mActivityTaskManager.getTasks(
+                    /* maxNum= */ Integer.MAX_VALUE,
+                    /* filterOnlyVisibleRecents= */ true,
+                    /* keepIntentExtra= */ false);
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Failed to fetch running tasks");
+            return null;
+        }
+
+        for (RunningTaskInfo taskInfo : runningTaskInfos) {
+            if (taskInfo.taskId == taskId) {
+                return taskInfo;
+            }
+        }
+
+        return null;
+    }
+
+    @VisibleForTesting
+    void takeScreenshot(int taskId, @NonNull AndroidFuture callback) {
+        synchronized (mLock) {
+            boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId);
+            if (!isTaskAssociatedWithGameSession) {
+                Slog.w(TAG, "No game session found for id: " + taskId);
+                callback.complete(GameScreenshotResult.createInternalErrorResult());
+                return;
+            }
+        }
+
+        mBackgroundExecutor.execute(() -> {
+            final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId);
+            if (bitmap == null) {
+                Slog.w(TAG, "Could not get bitmap for id: " + taskId);
+                callback.complete(GameScreenshotResult.createInternalErrorResult());
+            } else {
+                callback.complete(GameScreenshotResult.createSuccessResult(bitmap));
+            }
+        });
+    }
 }
diff --git a/services/core/java/com/android/server/app/GameSessionRecord.java b/services/core/java/com/android/server/app/GameSessionRecord.java
index e9daceb..a241812 100644
--- a/services/core/java/com/android/server/app/GameSessionRecord.java
+++ b/services/core/java/com/android/server/app/GameSessionRecord.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.service.games.IGameSession;
+import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import java.util.Objects;
 
@@ -37,26 +38,34 @@
     }
 
     private final int mTaskId;
+    private final State mState;
     private final ComponentName mRootComponentName;
     @Nullable
     private final IGameSession mIGameSession;
-    private final State mState;
+    @Nullable
+    private final SurfacePackage mSurfacePackage;
 
     static GameSessionRecord awaitingGameSessionRequest(int taskId,
             ComponentName rootComponentName) {
-        return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null,
-                State.NO_GAME_SESSION_REQUESTED);
+        return new GameSessionRecord(
+                taskId,
+                State.NO_GAME_SESSION_REQUESTED,
+                rootComponentName,
+                /* gameSession= */ null,
+                /* surfacePackage= */ null);
     }
 
     private GameSessionRecord(
             int taskId,
+            @NonNull State state,
             @NonNull ComponentName rootComponentName,
             @Nullable IGameSession gameSession,
-            @NonNull State state) {
+            @Nullable SurfacePackage surfacePackage) {
         this.mTaskId = taskId;
+        this.mState = state;
         this.mRootComponentName = rootComponentName;
         this.mIGameSession = gameSession;
-        this.mState = state;
+        this.mSurfacePackage = surfacePackage;
     }
 
     public boolean isAwaitingGameSessionRequest() {
@@ -65,8 +74,12 @@
 
     @NonNull
     public GameSessionRecord withGameSessionRequested() {
-        return new GameSessionRecord(mTaskId, mRootComponentName, /* gameSession=*/ null,
-                State.GAME_SESSION_REQUESTED);
+        return new GameSessionRecord(
+                mTaskId,
+                State.GAME_SESSION_REQUESTED,
+                mRootComponentName,
+                /* gameSession=*/ null,
+                /* surfacePackage=*/ null);
     }
 
     public boolean isGameSessionRequested() {
@@ -74,15 +87,20 @@
     }
 
     @NonNull
-    public GameSessionRecord withGameSession(@NonNull IGameSession gameSession) {
+    public GameSessionRecord withGameSession(
+            @NonNull IGameSession gameSession,
+            @NonNull SurfacePackage surfacePackage) {
         Objects.requireNonNull(gameSession);
-        return new GameSessionRecord(mTaskId, mRootComponentName, gameSession,
-                State.GAME_SESSION_ATTACHED);
+        return new GameSessionRecord(mTaskId,
+                State.GAME_SESSION_ATTACHED,
+                mRootComponentName,
+                gameSession,
+                surfacePackage);
     }
 
-    @Nullable
-    public IGameSession getGameSession() {
-        return mIGameSession;
+    @NonNull
+    public int getTaskId() {
+        return mTaskId;
     }
 
     @NonNull
@@ -90,17 +108,29 @@
         return mRootComponentName;
     }
 
+    @Nullable
+    public IGameSession getGameSession() {
+        return mIGameSession;
+    }
+
+    @Nullable
+    public SurfacePackage getSurfacePackage() {
+        return mSurfacePackage;
+    }
+
     @Override
     public String toString() {
         return "GameSessionRecord{"
                 + "mTaskId="
                 + mTaskId
+                + ", mState="
+                + mState
                 + ", mRootComponentName="
                 + mRootComponentName
                 + ", mIGameSession="
                 + mIGameSession
-                + ", mState="
-                + mState
+                + ", mSurfacePackage="
+                + mSurfacePackage
                 + '}';
     }
 
@@ -115,12 +145,16 @@
         }
 
         GameSessionRecord that = (GameSessionRecord) o;
-        return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName)
-                && Objects.equals(mIGameSession, that.mIGameSession) && mState == that.mState;
+        return mTaskId == that.mTaskId
+                && mState == that.mState
+                && mRootComponentName.equals(that.mRootComponentName)
+                && Objects.equals(mIGameSession, that.mIGameSession)
+                && Objects.equals(mSurfacePackage, that.mSurfacePackage);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTaskId, mRootComponentName, mIGameSession, mState);
+        return Objects.hash(
+                mTaskId, mState, mRootComponentName, mIGameSession, mState, mSurfacePackage);
     }
 }
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 0961fcb3..8a62c34 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
@@ -1453,6 +1469,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/biometrics/log/CallbackWithProbe.java b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
index c985d5d..f7b73688 100644
--- a/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/CallbackWithProbe.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 
 /**
  * Client monitor callback that exposes a probe.
@@ -27,7 +28,7 @@
  *
  * @param <T> probe type
  */
-public class CallbackWithProbe<T extends Probe> implements BaseClientMonitor.Callback {
+public class CallbackWithProbe<T extends Probe> implements ClientMonitorCallback {
     private final boolean mStartWithClient;
     private final T mProbe;
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index e29caa8..86d72ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -138,7 +138,7 @@
     }
 
     @Override
-    public void cancelWithoutStarting(@NonNull Callback callback) {
+    public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) {
         Slog.d(TAG, "cancelWithoutStarting: " + this);
 
         final int errorCode = BiometricConstants.BIOMETRIC_ERROR_CANCELED;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 0eb5aaf..35a0f57 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -91,7 +91,7 @@
 
     /**
      * Handles lifecycle, e.g. {@link BiometricScheduler},
-     * {@link com.android.server.biometrics.sensors.BaseClientMonitor.Callback} after authentication
+     * {@link ClientMonitorCallback} after authentication
      * results are known. Note that this happens asynchronously from (but shortly after)
      * {@link #onAuthenticated(BiometricAuthenticator.Identifier, boolean, ArrayList)} and allows
      * {@link CoexCoordinator} a chance to invoke/delay this event.
@@ -440,7 +440,7 @@
      * Start authentication
      */
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         final @LockoutTracker.LockoutMode int lockoutMode =
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 1248c8b..e1f7e2a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -29,8 +29,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.log.BiometricLogger;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.NoSuchElementException;
 
 /**
@@ -46,63 +44,6 @@
     // Counter used to distinguish between ClientMonitor instances to help debugging.
     private static int sCount = 0;
 
-    /**
-     * Interface that ClientMonitor holders should use to receive callbacks.
-     */
-    public interface Callback {
-        /**
-         * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
-         * the queue and becomes the current operation).
-         *
-         * @param clientMonitor Reference of the ClientMonitor that is starting.
-         */
-        default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-        }
-
-        /**
-         * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
-         * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
-         * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their
-         * implementation.
-         *
-         * @param clientMonitor Reference of the ClientMonitor that finished.
-         * @param success True if the operation completed successfully.
-         */
-        default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
-        }
-    }
-
-    /** Holder for wrapping multiple handlers into a single Callback. */
-    public static class CompositeCallback implements Callback {
-        @NonNull
-        private final List<Callback> mCallbacks;
-
-        public CompositeCallback(@NonNull Callback... callbacks) {
-            mCallbacks = new ArrayList<>();
-
-            for (Callback callback : callbacks) {
-                if (callback != null) {
-                    mCallbacks.add(callback);
-                }
-            }
-        }
-
-        @Override
-        public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                mCallbacks.get(i).onClientStarted(clientMonitor);
-            }
-        }
-
-        @Override
-        public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                boolean success) {
-            for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                mCallbacks.get(i).onClientFinished(clientMonitor, success);
-            }
-        }
-    }
-
     private final int mSequentialId;
     @NonNull private final Context mContext;
     private final int mTargetUserId;
@@ -120,7 +61,7 @@
 
     // Use an empty callback by default since delayed operations can receive events
     // before they are started and cause NPE in subclasses that access this field directly.
-    @NonNull protected Callback mCallback = new Callback() {
+    @NonNull protected ClientMonitorCallback mCallback = new ClientMonitorCallback() {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
             Slog.e(TAG, "mCallback onClientStarted: called before set (should not happen)");
@@ -134,18 +75,6 @@
     };
 
     /**
-     * @return A ClientMonitorEnum constant defined in biometrics.proto
-     */
-    public abstract int getProtoEnum();
-
-    /**
-     * @return True if the ClientMonitor should cancel any current and pending interruptable clients
-     */
-    public boolean interruptsPrecedingClients() {
-        return false;
-    }
-
-    /**
      * @param context    system_server context
      * @param token      a unique token for the client
      * @param listener   recipient of related events (e.g. authentication)
@@ -189,11 +118,19 @@
         }
     }
 
+    /** A ClientMonitorEnum constant defined in biometrics.proto */
+    public abstract int getProtoEnum();
+
+    /** True if the ClientMonitor should cancel any current and pending interruptable clients. */
+    public boolean interruptsPrecedingClients() {
+        return false;
+    }
+
     /**
      * Starts the ClientMonitor's lifecycle.
      * @param callback invoked when the operation is complete (succeeds, fails, etc)
      */
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         mCallback = wrapCallbackForStart(callback);
         mCallback.onClientStarted(this);
     }
@@ -204,7 +141,7 @@
      * Returns the original callback unless overridden.
      */
     @NonNull
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
         return callback;
     }
 
@@ -329,7 +266,7 @@
     }
 
     @VisibleForTesting
-    public Callback getCallback() {
+    public ClientMonitorCallback getCallback() {
         return mCallback;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 39c5944..1a6da94 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -160,7 +160,7 @@
     // Internal callback, notified when an operation is complete. Notifies the requester
     // that the operation is complete, before performing internal scheduler work (such as
     // starting the next client).
-    private final BaseClientMonitor.Callback mInternalCallback = new BaseClientMonitor.Callback() {
+    private final ClientMonitorCallback mInternalCallback = new ClientMonitorCallback() {
         @Override
         public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
             Slog.d(getTag(), "[Started] " + clientMonitor);
@@ -247,7 +247,7 @@
     }
 
     @VisibleForTesting
-    public BaseClientMonitor.Callback getInternalCallback() {
+    public ClientMonitorCallback getInternalCallback() {
         return mInternalCallback;
     }
 
@@ -368,7 +368,7 @@
      * @param clientCallback optional callback, invoked when the client state changes.
      */
     public void scheduleClientMonitor(@NonNull BaseClientMonitor clientMonitor,
-            @Nullable BaseClientMonitor.Callback clientCallback) {
+            @Nullable ClientMonitorCallback clientCallback) {
         // If the incoming operation should interrupt preceding clients, mark any interruptable
         // pending clients as canceling. Once they reach the head of the queue, the scheduler will
         // send ERROR_CANCELED and skip the operation.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index e8b50d9..812ca8a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -65,7 +65,7 @@
     protected static final int STATE_WAITING_FOR_COOKIE = 4;
 
     /**
-     * The {@link BaseClientMonitor.Callback} has been invoked and the client is finished.
+     * The {@link ClientMonitorCallback} has been invoked and the client is finished.
      */
     protected static final int STATE_FINISHED = 5;
 
@@ -83,7 +83,7 @@
     @NonNull
     private final BaseClientMonitor mClientMonitor;
     @Nullable
-    private final BaseClientMonitor.Callback mClientCallback;
+    private final ClientMonitorCallback mClientCallback;
     @OperationState
     private int mState;
     @VisibleForTesting
@@ -92,14 +92,14 @@
 
     BiometricSchedulerOperation(
             @NonNull BaseClientMonitor clientMonitor,
-            @Nullable BaseClientMonitor.Callback callback
+            @Nullable ClientMonitorCallback callback
     ) {
         this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
     }
 
     protected BiometricSchedulerOperation(
             @NonNull BaseClientMonitor clientMonitor,
-            @Nullable BaseClientMonitor.Callback callback,
+            @Nullable ClientMonitorCallback callback,
             @OperationState int state
     ) {
         mClientMonitor = clientMonitor;
@@ -139,7 +139,7 @@
      * @param callback lifecycle callback
      * @return if this operation started
      */
-    public boolean start(@NonNull BaseClientMonitor.Callback callback) {
+    public boolean start(@NonNull ClientMonitorCallback callback) {
         checkInState("start",
                 STATE_WAITING_IN_QUEUE,
                 STATE_WAITING_FOR_COOKIE,
@@ -159,7 +159,7 @@
      * @param cookie   cookie indicting the operation should begin
      * @return if this operation started
      */
-    public boolean startWithCookie(@NonNull BaseClientMonitor.Callback callback, int cookie) {
+    public boolean startWithCookie(@NonNull ClientMonitorCallback callback, int cookie) {
         checkInState("start",
                 STATE_WAITING_IN_QUEUE,
                 STATE_WAITING_FOR_COOKIE,
@@ -173,8 +173,8 @@
         return doStart(callback);
     }
 
-    private boolean doStart(@NonNull BaseClientMonitor.Callback callback) {
-        final BaseClientMonitor.Callback cb = getWrappedCallback(callback);
+    private boolean doStart(@NonNull ClientMonitorCallback callback) {
+        final ClientMonitorCallback cb = getWrappedCallback(callback);
 
         if (mState == STATE_WAITING_IN_QUEUE_CANCELING) {
             Slog.d(TAG, "Operation marked for cancellation, cancelling now: " + this);
@@ -239,9 +239,9 @@
      *
      * @param handler handler to use for the cancellation watchdog
      * @param callback lifecycle callback (only used if this operation hasn't started, otherwise
-     *                 the callback used from {@link #start(BaseClientMonitor.Callback)} is used)
+     *                 the callback used from {@link #start(ClientMonitorCallback)} is used)
      */
-    public void cancel(@NonNull Handler handler, @NonNull BaseClientMonitor.Callback callback) {
+    public void cancel(@NonNull Handler handler, @NonNull ClientMonitorCallback callback) {
         checkNotInState("cancel", STATE_FINISHED);
 
         final int currentState = mState;
@@ -270,14 +270,14 @@
     }
 
     @NonNull
-    private BaseClientMonitor.Callback getWrappedCallback() {
+    private ClientMonitorCallback getWrappedCallback() {
         return getWrappedCallback(null);
     }
 
     @NonNull
-    private BaseClientMonitor.Callback getWrappedCallback(
-            @Nullable BaseClientMonitor.Callback callback) {
-        final BaseClientMonitor.Callback destroyCallback = new BaseClientMonitor.Callback() {
+    private ClientMonitorCallback getWrappedCallback(
+            @Nullable ClientMonitorCallback callback) {
+        final ClientMonitorCallback destroyCallback = new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
@@ -286,7 +286,7 @@
                 mState = STATE_FINISHED;
             }
         };
-        return new BaseClientMonitor.CompositeCallback(destroyCallback, callback, mClientCallback);
+        return new ClientMonitorCompositeCallback(destroyCallback, callback, mClientCallback);
     }
 
     /** {@link BaseClientMonitor#getSensorId()}. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
new file mode 100644
index 0000000..8ea4ee9
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
@@ -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 com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface that ClientMonitor holders should use to receive callbacks.
+ */
+public interface ClientMonitorCallback {
+    /**
+     * Invoked when the ClientMonitor operation has been started (e.g. reached the head of
+     * the queue and becomes the current operation).
+     *
+     * @param clientMonitor Reference of the ClientMonitor that is starting.
+     */
+    default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {}
+
+    /**
+     * Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
+     * (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
+     * revokeChallenge) so that a scheduler can process ClientMonitors regardless of their
+     * implementation.
+     *
+     * @param clientMonitor Reference of the ClientMonitor that finished.
+     * @param success       True if the operation completed successfully.
+     */
+    default void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {}
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
new file mode 100644
index 0000000..b82f5fa
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
@@ -0,0 +1,53 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Holder for wrapping multiple handlers into a single Callback. */
+public class ClientMonitorCompositeCallback implements ClientMonitorCallback {
+    @NonNull
+    private final List<ClientMonitorCallback> mCallbacks;
+
+    public ClientMonitorCompositeCallback(@NonNull ClientMonitorCallback... callbacks) {
+        mCallbacks = new ArrayList<>();
+
+        for (ClientMonitorCallback callback : callbacks) {
+            if (callback != null) {
+                mCallbacks.add(callback);
+            }
+        }
+    }
+
+    @Override
+    public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onClientStarted(clientMonitor);
+        }
+    }
+
+    @Override
+    public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+            boolean success) {
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            mCallbacks.get(i).onClientFinished(clientMonitor, success);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index c83323a..3b7adc1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -98,7 +98,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         if (hasReachedEnrollmentLimit()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
index c2f909b..3060f30 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -23,7 +23,7 @@
 
     /**
      * Callers should typically check this after
-     * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+     * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)}
      *
      * @return true if the user has gone from:
      *      1) none-enrolled --> enrolled
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 3d74f36..6fb6d08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -47,7 +47,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
index 63cd412..c8830f8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java
@@ -45,7 +45,7 @@
     /**
      * Invoked if the scheduler is unable to start the ClientMonitor (for example the HAL is null).
      * If such a problem is detected, the scheduler will not invoke
-     * {@link #start(Callback)}.
+     * {@link #start(ClientMonitorCallback)}.
      */
     public abstract void unableToStart();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 82a8437..0636893 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -64,7 +64,7 @@
     private final boolean mHasEnrollmentsBeforeStarting;
     private BaseClientMonitor mCurrentTask;
 
-    private final Callback mEnumerateCallback = new Callback() {
+    private final ClientMonitorCallback mEnumerateCallback = new ClientMonitorCallback() {
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             final List<BiometricAuthenticator.Identifier> unknownHALTemplates =
@@ -90,7 +90,7 @@
         }
     };
 
-    private final Callback mRemoveCallback = new Callback() {
+    private final ClientMonitorCallback mRemoveCallback = new ClientMonitorCallback() {
         @Override
         public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
             Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success);
@@ -139,7 +139,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         // Start enumeration. Removal will start if necessary, when enumeration is completed.
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index ced464e..05ea19a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -72,7 +72,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
index d5093c75..4f645ef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
@@ -29,15 +29,15 @@
 
     /**
      * Notifies the client that it needs to finish before
-     * {@link BaseClientMonitor#start(BaseClientMonitor.Callback)} was invoked. This usually happens
+     * {@link BaseClientMonitor#start(ClientMonitorCallback)} was invoked. This usually happens
      * if the client is still waiting in the pending queue and got notified that a subsequent
      * operation is preempting it.
      *
      * This method must invoke
-     * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)} on the
+     * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)} on the
      * given callback (with success).
      *
      * @param callback invoked when the operation is completed.
      */
-    void cancelWithoutStarting(@NonNull BaseClientMonitor.Callback callback);
+    void cancelWithoutStarting(@NonNull ClientMonitorCallback callback);
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
index cede4a7..ee6bb0f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java
@@ -62,7 +62,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
index 5ba1b00..b2661a2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java
@@ -84,7 +84,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         mUtils.setInvalidationInProgress(getContext(), getTargetUserId(), true /* inProgress */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 2a6677e..e79819b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -17,7 +17,6 @@
 package com.android.server.biometrics.sensors;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
@@ -59,7 +58,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         // The biometric template ids will be removed when we get confirmation from the HAL
diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
index 1edf5af..21a6ddf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java
@@ -38,7 +38,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 603cc22..4f90020 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -56,7 +56,7 @@
     @NonNull private final UserSwitchCallback mUserSwitchCallback;
     @Nullable private StopUserClient<?> mStopUserClient;
 
-    private class ClientFinishedCallback implements BaseClientMonitor.Callback {
+    private class ClientFinishedCallback implements ClientMonitorCallback {
         private final BaseClientMonitor mOwner;
 
         ClientFinishedCallback(BaseClientMonitor owner) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 77e431c..1e9b72b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -29,7 +29,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
-import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -137,7 +137,7 @@
     void startPreparedClient(int sensorId, int cookie);
 
     void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback);
+            @Nullable ClientMonitorCallback callback);
 
     void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 66b942b..8998269 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -35,6 +35,7 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.HashSet;
@@ -221,7 +222,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         Slog.d(TAG, "cleanupInternalState: " + userId);
-        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                 try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 757a52cb..dc21a04f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -39,7 +39,9 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -97,15 +99,15 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         mState = STATE_STARTED;
     }
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 2158dfe..72a20db07 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -30,6 +30,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.DetectionConsumer;
 
@@ -58,7 +59,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index b5f89b4..5c57dbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -41,7 +41,9 @@
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.face.FaceService;
 import com.android.server.biometrics.sensors.face.FaceUtils;
@@ -67,8 +69,8 @@
     private final int mMaxTemplatesPerUser;
     private final boolean mDebugConsent;
 
-    private final BaseClientMonitor.Callback mPreviewHandleDeleterCallback =
-            new BaseClientMonitor.Callback() {
+    private final ClientMonitorCallback mPreviewHandleDeleterCallback =
+            new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                 }
@@ -101,7 +103,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         BiometricNotificationUtils.cancelReEnrollNotification(getContext());
@@ -109,8 +111,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(mPreviewHandleDeleterCallback,
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(mPreviewHandleDeleterCallback,
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
index af826c2..584b58c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
@@ -48,7 +49,7 @@
         // Nothing to do here
     }
 
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index 315ede8b..acf5720 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -29,6 +29,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -60,7 +61,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index ae507ab..9d7a552 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -49,6 +49,7 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -217,7 +218,7 @@
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
-            BaseClientMonitor.Callback callback) {
+            ClientMonitorCallback callback) {
         if (!mSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
@@ -341,7 +342,7 @@
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
                     debugConsent);
-            scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
@@ -511,7 +512,7 @@
 
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
+            @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
             final FaceInternalCleanupClient client =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 1e1b532..fd44c5c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -27,6 +27,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -64,7 +65,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
index 4515d04..ee6982a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java
@@ -28,6 +28,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -65,7 +66,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index 2b5f495..4a3da0d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
 public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
@@ -43,7 +44,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
index 06328e3..88b9235 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
 public class FaceStopUserClient extends StopUserClient<ISession> {
@@ -36,7 +37,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index b45578b..e7483b3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -32,6 +32,7 @@
 
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.face.FaceUtils;
 
 import java.util.ArrayList;
@@ -197,7 +198,7 @@
     public void cleanupInternalState(int userId) {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFace10.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+        mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                 try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index e957794..9a52db1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -60,6 +60,7 @@
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
@@ -534,7 +535,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     opPackageName, mSensorId, sSystemClock.millis());
             mGeneratedChallengeCache = client;
-            mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                     if (client != clientMonitor) {
@@ -562,7 +563,7 @@
 
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, userId, opPackageName, mSensorId);
-            mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
@@ -591,7 +592,7 @@
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId);
 
-            mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
@@ -742,7 +743,7 @@
             final int faceId = faces.get(0).getBiometricId();
             final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
                     token, listener, userId, opPackageName, mSensorId, feature, faceId);
-            mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientFinished(
                         @NonNull BaseClientMonitor clientMonitor, boolean success) {
@@ -760,7 +761,7 @@
     }
 
     private void scheduleInternalCleanup(int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
+            @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -774,7 +775,7 @@
 
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
+            @Nullable ClientMonitorCallback callback) {
         scheduleInternalCleanup(userId, callback);
     }
 
@@ -890,7 +891,7 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
                 hasEnrolled, mAuthenticatorIds);
-        mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 80faf3e..1e0e799 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -34,7 +34,9 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.face.UsageStats;
 
@@ -87,15 +89,15 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         mState = STATE_STARTED;
     }
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 5c69d6f..8068e14 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -33,7 +33,9 @@
 import com.android.internal.R;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 
 import java.util.ArrayList;
@@ -69,8 +71,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
index f418104..e29a192 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.GenerateChallengeClient;
 
@@ -39,7 +40,7 @@
 
     private static final String TAG = "FaceGenerateChallengeClient";
     static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
-    private static final Callback EMPTY_CALLBACK = new Callback() {
+    private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() {
     };
 
     private final long mCreatedAt;
@@ -94,7 +95,7 @@
     }
 
     private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
-            @NonNull Callback ownerCallback) {
+            @NonNull ClientMonitorCallback ownerCallback) {
         Preconditions.checkState(mChallengeResult != null, "result not available");
         try {
             receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
index 7821601..0a9d96d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
@@ -28,6 +28,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -66,7 +67,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
index 9d977d6..ee01c43 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.ArrayList;
@@ -57,7 +58,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
index cc3d8f0..ee28f7b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
@@ -26,6 +26,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
@@ -71,7 +72,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         startHalOperation();
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 5343d0d..8ee8ce5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.io.File;
@@ -49,7 +50,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index be0e6ed..04fd534 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -31,6 +31,7 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.EnrollmentModifier;
 
@@ -39,7 +40,7 @@
 /**
  * A callback for receiving notifications about changes in fingerprint state.
  */
-public class FingerprintStateCallback implements BaseClientMonitor.Callback {
+public class FingerprintStateCallback implements ClientMonitorCallback {
 
     @NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
             mFingerprintStateListeners = new CopyOnWriteArrayList<>();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 535705c..0bdc4eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -30,7 +30,7 @@
 import android.os.IBinder;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
 
@@ -121,7 +121,7 @@
             @NonNull String opPackageName);
 
     void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback);
+            @Nullable ClientMonitorCallback callback);
 
     boolean isHardwareDetected(int sensorId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 2b50b96..b29fbb6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -32,6 +32,7 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
@@ -204,7 +205,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         Slog.d(TAG, "cleanupInternalState: " + userId);
-        mProvider.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+        mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                 try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 96f4853..f3d0121 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -37,7 +37,9 @@
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -86,7 +88,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         if (mSensorProps.isAnyUdfpsType()) {
@@ -99,8 +101,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(mALSProbeCallback, callback);
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index ac3ce89..1f0482d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -30,6 +30,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.DetectionConsumer;
 import com.android.server.biometrics.sensors.SensorOverlays;
@@ -61,7 +62,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index e3f26df..169c3eb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -37,7 +37,9 @@
 import com.android.server.biometrics.HardwareAuthTokenUtils;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -82,8 +84,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ed2345e..52bd234 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -24,6 +24,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.util.Map;
@@ -48,7 +49,7 @@
         // Nothing to do here
     }
 
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index eb16c76..efc9304 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -55,7 +55,9 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.InvalidationRequesterClient;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -248,7 +250,7 @@
     }
 
     private void scheduleForSensor(int sensorId, @NonNull BaseClientMonitor client,
-            BaseClientMonitor.Callback callback) {
+            ClientMonitorCallback callback) {
         if (!mSensors.contains(sensorId)) {
             throw new IllegalStateException("Unable to schedule client: " + client
                     + " for sensor: " + sensorId);
@@ -361,7 +363,7 @@
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     mSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
-            scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+            scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
 
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -484,7 +486,7 @@
 
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
+            @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
             final FingerprintInternalCleanupClient client =
@@ -493,7 +495,7 @@
                             mContext.getOpPackageName(), sensorId, enrolledList,
                             FingerprintUtils.getInstance(sensorId),
                             mSensors.get(sensorId).getAuthenticatorIds());
-            scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+            scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
                     mFingerprintStateCallback));
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index 878ef46..ee8d170 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -27,6 +27,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutCache;
@@ -64,7 +65,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index ee81620..9f11df6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StartUserClient;
 
 public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
@@ -44,7 +45,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
index 7055d65..9d38145 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.StopUserClient;
 
 public class FingerprintStopUserClient extends StopUserClient<ISession> {
@@ -36,7 +37,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 79c6b1b3..033855f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -31,6 +31,7 @@
 
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 
@@ -201,7 +202,7 @@
     public void cleanupInternalState(int userId)  {
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
-        mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new BaseClientMonitor.Callback() {
+        mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                 try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 6feb5fa..f160dff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -62,7 +62,9 @@
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
 import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -492,7 +494,7 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
-        mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                     boolean success) {
@@ -577,7 +579,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
                     mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController,
                     enrollReason);
-            mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
+            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                     mFingerprintStateCallback.onClientStarted(clientMonitor);
@@ -699,7 +701,7 @@
     }
 
     private void scheduleInternalCleanup(int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
+            @Nullable ClientMonitorCallback callback) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -715,8 +717,8 @@
 
     @Override
     public void scheduleInternalCleanup(int sensorId, int userId,
-            @Nullable BaseClientMonitor.Callback callback) {
-        scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+            @Nullable ClientMonitorCallback callback) {
+        scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
                 mFingerprintStateCallback));
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 38fe73f..1694bd9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -414,7 +414,8 @@
     }
 
     @Override
-    public void onTrustChanged(boolean enabled, int userId, int flags) {
+    public void onTrustChanged(boolean enabled, int userId, int flags,
+            List<String> trustGrantedMessages) {
         mUserHasTrust.put(userId, enabled);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index d9b290f..87d47c1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -36,7 +36,9 @@
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.LockoutTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -86,7 +88,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         if (mSensorProps.isAnyUdfpsType()) {
@@ -99,8 +101,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(mALSProbeCallback, callback);
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index f1dec66..9137212 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -32,6 +32,7 @@
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.PerformanceTracker;
 import com.android.server.biometrics.sensors.SensorOverlays;
@@ -82,7 +83,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         startHalOperation();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index dd92e3e..82b046d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -33,7 +33,9 @@
 
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
 import com.android.server.biometrics.sensors.EnrollClient;
 import com.android.server.biometrics.sensors.SensorOverlays;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -75,8 +77,8 @@
 
     @NonNull
     @Override
-    protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(
+    protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
+        return new ClientMonitorCompositeCallback(
                 getLogger().createALSCallback(true /* startWithClient */), callback);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
index a39f4f8..ed28e3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
@@ -22,6 +22,7 @@
 
 import com.android.server.biometrics.BiometricsProto;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 
 /**
  * Clears lockout, which is handled in the framework (and not the HAL) for the
@@ -40,7 +41,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
         mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
                 getTargetUserId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a2c1892..d317984 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -27,6 +27,7 @@
 import android.util.Slog;
 
 import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 
 import java.io.File;
@@ -62,7 +63,7 @@
     }
 
     @Override
-    public void start(@NonNull Callback callback) {
+    public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
         if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 9d9b90c..1e00ea9 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.hardware.CameraSessionStats;
 import android.hardware.CameraStreamStats;
 import android.hardware.ICameraService;
@@ -46,6 +47,8 @@
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
 import android.os.Binder;
@@ -303,6 +306,9 @@
 
         @Override
         public void onFixedRotationFinished(int displayId) { }
+
+        @Override
+        public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { }
     }
 
 
@@ -335,6 +341,16 @@
                         switchUserLocked(mLastUser);
                     }
                     break;
+                case UsbManager.ACTION_USB_DEVICE_ATTACHED:
+                case UsbManager.ACTION_USB_DEVICE_DETACHED:
+                    synchronized (mLock) {
+                        UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                        if (device != null) {
+                            notifyUsbDeviceHotplugLocked(device,
+                                    action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED));
+                        }
+                    }
+                    break;
                 default:
                     break; // do nothing
             }
@@ -645,6 +661,8 @@
         filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
         mContext.registerReceiver(mIntentReceiver, filter);
 
         publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy);
@@ -963,6 +981,32 @@
         return true;
     }
 
+    private boolean notifyUsbDeviceHotplugLocked(@NonNull UsbDevice device, boolean attached) {
+        // Only handle external USB camera devices
+        if (device.getHasVideoCapture()) {
+            // Forward the usb hotplug event to the native camera service running in the
+            // cameraserver
+            // process.
+            ICameraService cameraService = getCameraServiceRawLocked();
+            if (cameraService == null) {
+                Slog.w(TAG, "Could not notify cameraserver, camera service not available.");
+                return false;
+            }
+
+            try {
+                int eventType = attached ? ICameraService.EVENT_USB_DEVICE_ATTACHED
+                        : ICameraService.EVENT_USB_DEVICE_DETACHED;
+                mCameraServiceRaw.notifySystemEvent(eventType, new int[]{device.getDeviceId()});
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e);
+                // Not much we can do if camera service is dead.
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
     private void updateActivityCount(CameraSessionStats cameraState) {
         String cameraId = cameraState.getCameraId();
         int newCameraState = cameraState.getNewCameraState();
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 582dd7c..5b76695 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -81,6 +81,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -127,6 +128,7 @@
     private final IUriGrantsManager mUgm;
     private final UriGrantsManagerInternal mUgmInternal;
     private final WindowManagerInternal mWm;
+    private final VirtualDeviceManagerInternal mVdm;
     private final IUserManager mUm;
     private final PackageManager mPm;
     private final AppOpsManager mAppOps;
@@ -158,6 +160,8 @@
         mUgm = UriGrantsManager.getService();
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mWm = LocalServices.getService(WindowManagerInternal.class);
+        // Can be null; not all products have CDM + VirtualDeviceManager
+        mVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
         mPm = getContext().getPackageManager();
         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
@@ -973,6 +977,13 @@
         // First, verify package ownership to ensure use below is safe.
         mAppOps.checkPackage(uid, callingPackage);
 
+        // Nothing in a virtual session is permitted to touch clipboard contents
+        if (mVdm != null && mVdm.isAppRunningOnAnyVirtualDevice(uid)) {
+            Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage
+                    + " within a virtual device session");
+            return false;
+        }
+
         // Shell can access the clipboard for testing purposes.
         if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
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/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index fd4cd8e..35e3db78 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -358,6 +358,12 @@
     public float brightnessMaximum;
     public float brightnessDefault;
 
+    /**
+     * Install orientation of display panel relative to its natural orientation.
+     */
+    @Surface.Rotation
+    public int installOrientation = Surface.ROTATION_0;
+
     public void setAssumedDensityForExternalDisplay(int width, int height) {
         densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
         // Technically, these values should be smaller than the apparent density
@@ -417,7 +423,8 @@
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
                 other.brightnessDefault)
-                || !Objects.equals(roundedCorners, other.roundedCorners)) {
+                || !Objects.equals(roundedCorners, other.roundedCorners)
+                || installOrientation != other.installOrientation) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -461,6 +468,7 @@
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
         roundedCorners = other.roundedCorners;
+        installOrientation = other.installOrientation;
     }
 
     // For debugging purposes
@@ -508,6 +516,7 @@
             sb.append(", roundedCorners ").append(roundedCorners);
         }
         sb.append(flagsToString(flags));
+        sb.append(", installOrientation ").append(installOrientation);
         sb.append("}");
         return sb.toString();
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 34f915e..31c496ed 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -50,8 +50,6 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MathUtils;
-import android.util.MutableFloat;
-import android.util.MutableInt;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.view.Display;
@@ -911,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);
@@ -921,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);
@@ -1382,7 +1390,6 @@
 
         // Animate the screen brightness when the screen is on or dozing.
         // Skip the animation when the screen is off or suspended or transition to/from VR.
-        boolean brightnessAdjusted = false;
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
                 if (state == Display.STATE_ON) {
@@ -1475,19 +1482,15 @@
                     // slider event so notify as if the system changed the brightness.
                     userInitiatedChange = false;
                 }
-                notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+                notifyBrightnessChanged(brightnessState, userInitiatedChange,
                         hadUserBrightnessPoint);
             }
 
             // We save the brightness info *after* the brightness setting has been changed and
             // adjustments made so that the brightness info reflects the latest value.
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+            saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
         } else {
-            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
-        }
-
-        if (brightnessAdjusted) {
-            postBrightnessChangeRunnable();
+            saveBrightnessInfo(getScreenBrightnessSetting());
         }
 
         // Log any changes to what is currently driving the brightness setting.
@@ -1603,50 +1606,31 @@
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
-                    mCachedBrightnessInfo.brightness.value,
-                    mCachedBrightnessInfo.adjustedBrightness.value,
-                    mCachedBrightnessInfo.brightnessMin.value,
-                    mCachedBrightnessInfo.brightnessMax.value,
-                    mCachedBrightnessInfo.hbmMode.value,
-                    mCachedBrightnessInfo.hbmTransitionPoint.value);
+                    mCachedBrightnessInfo.brightness,
+                    mCachedBrightnessInfo.adjustedBrightness,
+                    mCachedBrightnessInfo.brightnessMin,
+                    mCachedBrightnessInfo.brightnessMax,
+                    mCachedBrightnessInfo.hbmMode,
+                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
         }
     }
 
-    private boolean saveBrightnessInfo(float brightness) {
-        return saveBrightnessInfo(brightness, brightness);
+    private void saveBrightnessInfo(float brightness) {
+        saveBrightnessInfo(brightness, brightness);
     }
 
-    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+    private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
         synchronized (mCachedBrightnessInfo) {
-            boolean changed = false;
-
-            changed |=
-                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
-                        brightness);
-            changed |=
-                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
-                        adjustedBrightness);
-            changed |=
-                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
-                        mHbmController.getCurrentBrightnessMin());
-            changed |=
-                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
-                        mHbmController.getCurrentBrightnessMax());
-            changed |=
-                mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
-                        mHbmController.getHighBrightnessMode());
-            changed |=
-                mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
-                        mHbmController.getTransitionPoint());
-
-            return changed;
+            mCachedBrightnessInfo.brightness = brightness;
+            mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
+            mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
+            mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
+            mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+            mCachedBrightnessInfo.highBrightnessTransitionPoint =
+                mHbmController.getTransitionPoint();
         }
     }
 
-    void postBrightnessChangeRunnable() {
-        mHandler.post(mOnBrightnessChangeRunnable);
-    }
-
     private HighBrightnessModeController createHbmControllerLocked() {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
@@ -1661,7 +1645,7 @@
                 displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 () -> {
                     sendUpdatePowerStateLocked();
-                    postBrightnessChangeRunnable();
+                    mHandler.post(mOnBrightnessChangeRunnable);
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     if (mAutomaticBrightnessController != null) {
                         mAutomaticBrightnessController.update();
@@ -2163,7 +2147,7 @@
     private void setCurrentScreenBrightness(float brightnessValue) {
         if (brightnessValue != mCurrentScreenBrightnessSetting) {
             mCurrentScreenBrightnessSetting = brightnessValue;
-            postBrightnessChangeRunnable();
+            mHandler.post(mOnBrightnessChangeRunnable);
         }
     }
 
@@ -2215,7 +2199,7 @@
         return true;
     }
 
-    private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
+    private void notifyBrightnessChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
         if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
@@ -2325,17 +2309,16 @@
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
         synchronized (mCachedBrightnessInfo) {
-            pw.println("  mCachedBrightnessInfo.brightness=" +
-                    mCachedBrightnessInfo.brightness.value);
+            pw.println("  mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
             pw.println("  mCachedBrightnessInfo.adjustedBrightness=" +
-                    mCachedBrightnessInfo.adjustedBrightness.value);
+                    mCachedBrightnessInfo.adjustedBrightness);
             pw.println("  mCachedBrightnessInfo.brightnessMin=" +
-                    mCachedBrightnessInfo.brightnessMin.value);
+                    mCachedBrightnessInfo.brightnessMin);
             pw.println("  mCachedBrightnessInfo.brightnessMax=" +
-                    mCachedBrightnessInfo.brightnessMax.value);
-            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
-            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint=" +
-                    mCachedBrightnessInfo.hbmTransitionPoint.value);
+                    mCachedBrightnessInfo.brightnessMax);
+            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
+            pw.println("  mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
+                    mCachedBrightnessInfo.highBrightnessTransitionPoint);
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2493,10 +2476,7 @@
     private void reportStats(float brightness) {
         float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
         synchronized(mCachedBrightnessInfo) {
-            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
-                return;
-            }
-            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+            hbmTransitionPoint = mCachedBrightnessInfo.highBrightnessTransitionPoint;
         }
 
         final boolean aboveTransition = brightness > hbmTransitionPoint;
@@ -2793,31 +2773,11 @@
     }
 
     static class CachedBrightnessInfo {
-        public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat adjustedBrightness =
-            new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat brightnessMin =
-            new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableFloat brightnessMax =
-            new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
-        public MutableFloat hbmTransitionPoint =
-            new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
-
-        public boolean checkAndSetFloat(MutableFloat mf, float f) {
-            if (mf.value != f) {
-                mf.value = f;
-                return true;
-            }
-            return false;
-        }
-
-        public boolean checkAndSetInt(MutableInt mi, int i) {
-            if (mi.value != i) {
-                mi.value = i;
-                return true;
-            }
-            return false;
-        }
+        public float brightness;
+        public float adjustedBrightness;
+        public float brightnessMin;
+        public float brightnessMax;
+        public int hbmMode;
+        public float highBrightnessTransitionPoint;
     }
 }
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/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 84de822..3a9ef0a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -641,6 +641,7 @@
 
                 mInfo.roundedCorners = RoundedCorners.fromResources(
                         res, mInfo.uniqueId, mInfo.width, mInfo.height);
+                mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
 
                 if (mStaticDisplayInfo.isInternal) {
                     mInfo.type = Display.TYPE_INTERNAL;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 4d1367a3..e3ecf49 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -429,6 +429,7 @@
             mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
             mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
+            mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 7719dfe..93c73be 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -397,12 +397,16 @@
                 // We already told the displays to turn off, now we need to wake the device as
                 // we transition to this new state. We do it here so that the waking happens
                 // between the transition from one layout to another.
-                mPowerManager.wakeUp(SystemClock.uptimeMillis(),
-                        PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold");
+                mHandler.post(() -> {
+                    mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+                            PowerManager.WAKE_REASON_UNFOLD_DEVICE, "server.display:unfold");
+                });
             } else if (sleepDevice) {
                 // Send the device to sleep when required.
-                mPowerManager.goToSleep(SystemClock.uptimeMillis(),
-                        PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0);
+                mHandler.post(() -> {
+                    mPowerManager.goToSleep(SystemClock.uptimeMillis(),
+                            PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD, 0);
+                });
             }
         }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 261aa32..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;
@@ -38,6 +40,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayViewport;
@@ -269,6 +272,10 @@
     private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>();
     @GuardedBy("mAssociationLock")
     private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+    private final Object mPointerDisplayIdLock = new Object();
+    // Forces the MouseCursorController to target a specific display id.
+    @GuardedBy("mPointerDisplayIdLock")
+    private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
 
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -284,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);
@@ -308,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);
@@ -341,6 +351,9 @@
     private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
     private static native void nativeNotifyPortAssociationsChanged(long ptr);
     private static native void nativeChangeUniqueIdAssociation(long ptr);
+    private static native void nativeNotifyPointerDisplayIdChanged(long ptr);
+    private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId,
+            boolean enabled);
     private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
     private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId);
     private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType);
@@ -658,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
@@ -1769,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,
@@ -1902,6 +1935,18 @@
         return result;
     }
 
+    private void setVirtualMousePointerDisplayId(int displayId) {
+        synchronized (mPointerDisplayIdLock) {
+            mOverriddenPointerDisplayId = displayId;
+        }
+        // TODO(b/215597605): trigger MousePositionTracker update
+        nativeNotifyPointerDisplayIdChanged(mPtr);
+    }
+
+    private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
+        nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible);
+    }
+
     private static class VibrationInfo {
         private final long[] mPattern;
         private final int[] mAmplitudes;
@@ -2575,6 +2620,7 @@
         synchronized (mInputFilterLock) { }
         synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
         synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
+        synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ }
         nativeMonitor(mPtr);
     }
 
@@ -2965,6 +3011,12 @@
 
     // Native callback.
     private int getPointerDisplayId() {
+        synchronized (mPointerDisplayIdLock) {
+            // Prefer the override to all other displays.
+            if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
+                return mOverriddenPointerDisplayId;
+            }
+        }
         return mWindowManagerCallbacks.getPointerDisplayId();
     }
 
@@ -3109,6 +3161,9 @@
 
         int getPointerDisplayId();
 
+        /** Gets the x and y coordinates of the cursor's current position. */
+        PointF getCursorPosition();
+
         /**
          * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event
          * occurred on a window that did not have focus.
@@ -3427,6 +3482,26 @@
         }
 
         @Override
+        public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
+            InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
+        }
+
+        @Override
+        public PointF getCursorPosition() {
+            return mWindowManagerCallbacks.getCursorPosition();
+        }
+
+        @Override
+        public void setPointerAcceleration(float acceleration) {
+            InputManagerService.this.setPointerAcceleration(acceleration);
+        }
+
+        @Override
+        public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
+            InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible);
+        }
+
+        @Override
         public void registerLidSwitchCallback(LidSwitchCallback callbacks) {
             registerLidSwitchCallbackInternal(callbacks);
         }
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 db13deb..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!");
@@ -309,8 +309,8 @@
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
                     mSupportsStylusHw = info.supportsStylusHandwriting();
-                    mService.executeOrSendInitializeIme(mCurMethod, mCurToken,
-                            info.getConfigChanges(), mSupportsStylusHw);
+                    mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges(),
+                            mSupportsStylusHw);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                 }
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 ec49b47..8a6aa0d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -97,7 +97,6 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.IInterface;
 import android.os.LocaleList;
 import android.os.Message;
 import android.os.Parcel;
@@ -161,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;
@@ -220,22 +217,11 @@
     }
 
     private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
-    private static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
-    private static final int MSG_SHOW_IM_CONFIG = 3;
 
-    private static final int MSG_UNBIND_INPUT = 1000;
-    private static final int MSG_BIND_INPUT = 1010;
-    private static final int MSG_SHOW_SOFT_INPUT = 1020;
-    private static final int MSG_HIDE_SOFT_INPUT = 1030;
     private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
-    private static final int MSG_INITIALIZE_IME = 1040;
-    private static final int MSG_CREATE_SESSION = 1050;
     private static final int MSG_REMOVE_IME_SURFACE = 1060;
     private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
     private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
-    private static final int MSG_START_HANDWRITING = 1100;
-
-    private static final int MSG_START_INPUT = 2000;
 
     private static final int MSG_UNBIND_CLIENT = 3000;
     private static final int MSG_BIND_CLIENT = 3010;
@@ -248,8 +234,6 @@
     private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
     private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
 
-    private static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
-
     private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
@@ -285,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<>();
@@ -343,7 +326,7 @@
 
     static class SessionState {
         final ClientState client;
-        final IInputMethod method;
+        final IInputMethodInvoker method;
 
         IInputMethodSession session;
         InputChannel channel;
@@ -352,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;
@@ -627,7 +610,7 @@
      */
     @GuardedBy("ImfLock.class")
     @Nullable
-    private IInputMethod getCurMethodLocked() {
+    private IInputMethodInvoker getCurMethodLocked() {
         return mBindingController.getCurMethod();
     }
 
@@ -654,9 +637,9 @@
     boolean mBoundToMethod;
 
     /**
-     * Currently enabled session.  Only touched by service thread, not
-     * protected by a lock.
+     * Currently enabled session.
      */
+    @GuardedBy("ImfLock.class")
     SessionState mEnabledSession;
 
     /**
@@ -705,11 +688,11 @@
             new CopyOnWriteArrayList<>();
 
     /**
-     * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the
-     * internal message queue. Any subsequent state change inside {@link InputMethodManagerService}
-     * will not affect those tasks that are already posted.
+     * Internal state snapshot when
+     * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo,
+     * boolean)} is about to be called.
      *
-     * <p>Posting {@link #MSG_START_INPUT} message basically means that
+     * <p>Calling that IPC endpoint basically means that
      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
      * back in the current IME process shortly, which will also affect what the current IME starts
      * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
@@ -785,7 +768,7 @@
             final int mFocusedWindowSoftInputMode;
             @SoftInputShowHideReason
             final int mReason;
-            // The timing of handling MSG_SHOW_SOFT_INPUT or MSG_HIDE_SOFT_INPUT.
+            // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
             final long mTimestamp;
             final long mWallTime;
             final boolean mInFullscreenMode;
@@ -1541,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();
         }
     }
 
@@ -1575,7 +1558,7 @@
             mHandler.removeCallbacks(mUserSwitchHandlerTask);
         }
         // Hide soft input before user switch task since switch task may block main handler a while
-        // and delayed the MSG_HIDE_SOFT_INPUT.
+        // and delayed the hideCurrentInputLocked().
         hideCurrentInputLocked(
                 mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER);
         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
@@ -1602,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);
@@ -1782,8 +1763,8 @@
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
                 if (mShowOngoingImeSwitcherForPhones) {
                     mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> {
-                        mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0)
-                                .sendToTarget();
+                        mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
+                                available ? 1 : 0, 0 /* unused */).sendToTarget();
                     });
                 }
 
@@ -1965,15 +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) {
-                executeOrSendMessage(curMethod,
-                        mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod,
-                                requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback,
-                                        imi.getPackageName(), mCurTokenDisplayId,
-                                        getCurTokenLocked(),
-                                        this)));
+                final IInlineSuggestionsRequestCallback callbackImpl =
+                        new InlineSuggestionsRequestCallbackDecorator(callback,
+                                imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
+                                this);
+                curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
             } else {
                 callback.onInlineSuggestionsUnsupported();
             }
@@ -2201,10 +2181,9 @@
                             mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
-                        IInputMethod curMethod = getCurMethodLocked();
+                        IInputMethodInvoker curMethod = getCurMethodLocked();
                         if (curMethod != null) {
-                            executeOrSendMessage(curMethod, mCaller.obtainMessageO(
-                                    MSG_UNBIND_INPUT, curMethod));
+                            curMethod.unbindInput();
                         }
                     }
                     mCurClient = null;
@@ -2216,9 +2195,33 @@
         }
     }
 
-    private void executeOrSendMessage(IInterface target, Message msg) {
+    @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) {
-             mCaller.sendMessage(msg);
+             // This is supposed to be emulating the one-way semantics when the IME client is
+             // system_server itself, which has not been explicitly prohibited so far while we have
+             // never ever officially supported such a use case...
+             // 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.
+             msg.sendToTarget();
          } else {
              handleMessage(msg);
              msg.recycle();
@@ -2232,16 +2235,15 @@
                     + mCurClient.client.asBinder());
             if (mBoundToMethod) {
                 mBoundToMethod = false;
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null) {
-                    executeOrSendMessage(curMethod, mCaller.obtainMessageO(
-                            MSG_UNBIND_INPUT, curMethod));
+                    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;
@@ -2284,16 +2286,15 @@
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
         if (!mBoundToMethod) {
-            IInputMethod curMethod = getCurMethodLocked();
-            executeOrSendMessage(curMethod, mCaller.obtainMessageOO(
-                    MSG_BIND_INPUT, curMethod, mCurClient.binding));
+            getCurMethodLocked().bindInput(mCurClient.binding);
             mBoundToMethod = true;
         }
 
+        final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
         final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(),
                 getCurTokenLocked(),
-                mCurTokenDisplayId, getCurIdLocked(), startInputReason, !initial,
+                mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId,
                 mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
                 getSequenceNumberLocked());
@@ -2312,9 +2313,9 @@
         }
 
         final SessionState session = mCurClient.curSession;
-        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
-                MSG_START_INPUT, 0 /* unused */, initial ? 0 : 1 /* restarting */,
-                startInputToken, session, mCurInputContext, mCurAttribute));
+        setEnabledSessionLocked(session);
+        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
+
         if (mShowRequested) {
             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
             showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2330,11 +2331,20 @@
                 curId, getSequenceNumberLocked(), suppressesSpellChecker);
     }
 
+    /**
+     * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the
+     * selected InputMethod to the given focused IME client.
+     *
+     * Note that this should be called after validating if the IME client has IME focus.
+     *
+     * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int)
+     */
     @GuardedBy("ImfLock.class")
     @NonNull
-    InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
-            @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
-            @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) {
+    private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
+            IInputContext inputContext, @NonNull EditorInfo attribute,
+            @StartInputFlags int startInputFlags, @StartInputReason int startInputReason,
+            int unverifiedTargetSdkVersion) {
         // If no method is currently selected, do nothing.
         String selectedMethodId = getSelectedMethodIdLocked();
         if (selectedMethodId == null) {
@@ -2356,10 +2366,6 @@
             return InputBindResult.INVALID_PACKAGE_NAME;
         }
 
-        if (!mWindowManagerInternal.isUidAllowedOnDisplay(cs.selfReportedDisplayId, cs.uid)) {
-            // Wait, the client no longer has access to the display.
-            return InputBindResult.INVALID_DISPLAY_ID;
-        }
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
         mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
@@ -2501,21 +2507,27 @@
         }
     }
 
-    @AnyThread
-    void executeOrSendInitializeIme(@NonNull IInputMethod inputMethod, @NonNull IBinder token,
+    @GuardedBy("ImfLock.class")
+    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
             @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
-        executeOrSendMessage(inputMethod, mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME,
-                configChanges, inputMethod, token, supportStylusHw));
+        if (DEBUG) {
+            Slog.v(TAG, "Sending attach of token: " + token + " for display: "
+                    + mCurTokenDisplayId);
+        }
+        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) {
@@ -2525,7 +2537,7 @@
                     channel.dispose();
                     return;
                 }
-                IInputMethod curMethod = getCurMethodLocked();
+                IInputMethodInvoker curMethod = getCurMethodLocked();
                 if (curMethod != null && method != null
                         && curMethod.asBinder() == method.asBinder()) {
                     if (mCurClient != null) {
@@ -2535,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;
@@ -2579,22 +2591,38 @@
     void requestClientSessionLocked(ClientState cs) {
         if (!cs.sessionRequested) {
             if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
-            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
+            final InputChannel serverChannel;
+            final InputChannel clientChannel;
+            {
+                final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
+                serverChannel = channels[0];
+                clientChannel = channels[1];
+            }
+
             cs.sessionRequested = true;
-            IInputMethod curMethod = getCurMethodLocked();
-            executeOrSendMessage(curMethod, mCaller.obtainMessageOOO(
-                    MSG_CREATE_SESSION, curMethod, channels[1],
-                    new IInputSessionCallback.Stub() {
-                        @Override
-                        public void sessionCreated(IInputMethodSession session) {
-                            final long ident = Binder.clearCallingIdentity();
-                            try {
-                                onSessionCreated(curMethod, session, channels[0]);
-                            } finally {
-                                Binder.restoreCallingIdentity(ident);
-                            }
-                        }
-                    }));
+
+            final IInputMethodInvoker curMethod = getCurMethodLocked();
+            final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() {
+                @Override
+                public void sessionCreated(IInputMethodSession session) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        onSessionCreated(curMethod, session, serverChannel);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                }
+            };
+
+            try {
+                curMethod.createSession(clientChannel, callback);
+            } finally {
+                // Dispose the channel because the remote proxy will get its own copy when
+                // unparceled.
+                if (clientChannel != null) {
+                    clientChannel.dispose();
+                }
+            }
         }
     }
 
@@ -2975,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;
@@ -3059,9 +3083,9 @@
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
-                if (getCurMethodLocked() != null) {
-                    executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageIO(
-                            MSG_START_HANDWRITING, ++mHwRequestId, getCurMethodLocked()));
+                final IInputMethodInvoker curMethod = getCurMethodLocked();
+                if (curMethod != null) {
+                    curMethod.canStartStylusHandwriting(++mHwRequestId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -3112,18 +3136,24 @@
         }
 
         mBindingController.setCurrentMethodVisible();
-        if (getCurMethodLocked() != null) {
+        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);
-            IInputMethod curMethod = getCurMethodLocked();
-            executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
-                    getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
-                    showInputToken));
+            final int showFlags = getImeShowFlagsLocked();
+            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);
+            }
             mInputShown = true;
             return true;
         }
-
         return false;
     }
 
@@ -3147,14 +3177,11 @@
                     // be made before input is started in it.
                     final ClientState cs = mClients.get(client.asBinder());
                     if (cs == null) {
-                        throw new IllegalArgumentException(
-                                "unknown client " + client.asBinder());
+                        throw new IllegalArgumentException("unknown client " + client.asBinder());
                     }
-                    if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
-                            cs.selfReportedDisplayId)) {
+                    if (!isImeClientFocused(windowToken, cs)) {
                         if (DEBUG) {
-                            Slog.w(TAG,
-                                    "Ignoring hideSoftInput of uid " + uid + ": " + client);
+                            Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client);
                         }
                         return false;
                     }
@@ -3191,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;
@@ -3202,8 +3229,15 @@
             // delivered to the IME process as an IPC.  Hence the inconsistency between
             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
             // the final state.
-            executeOrSendMessage(curMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT,
-                    reason, curMethod, resultReceiver, hideInputToken));
+            if (DEBUG) {
+                Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
+                        + ", " + resultReceiver + ") for reason: "
+                        + InputMethodDebug.softInputDisplayReasonToString(reason));
+            }
+            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
+            if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
+                onShowHideSoftInputRequested(false /* show */, windowToken, reason);
+            }
             res = true;
         } else {
             res = false;
@@ -3216,6 +3250,12 @@
         return res;
     }
 
+    private boolean isImeClientFocused(IBinder windowToken, ClientState cs) {
+        final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+                windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId);
+        return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
+    }
+
     @NonNull
     @Override
     public InputBindResult startInputOrWindowGainedFocus(
@@ -3309,31 +3349,30 @@
                     + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
         }
 
-        final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
-
         final ClientState cs = mClients.get(client.asBinder());
         if (cs == null) {
             throw new IllegalArgumentException("unknown client " + client.asBinder());
         }
-        if (cs.selfReportedDisplayId != windowDisplayId) {
-            Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch."
-                    + " from client:" + cs.selfReportedDisplayId
-                    + " from window:" + windowDisplayId);
-            return InputBindResult.DISPLAY_ID_MISMATCH;
-        }
 
-        if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
-                cs.selfReportedDisplayId)) {
-            // Check with the window manager to make sure this client actually
-            // has a window with focus.  If not, reject.  This is thread safe
-            // because if the focus changes some time before or after, the
-            // next client receiving focus that has any interest in input will
-            // be calling through here after that change happens.
-            if (DEBUG) {
-                Slog.w(TAG, "Focus gain on non-focused client " + cs.client
-                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
-            }
-            return InputBindResult.NOT_IME_TARGET_WINDOW;
+        final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus(
+                windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId);
+        switch (imeClientFocus) {
+            case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH:
+                Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch.");
+                return InputBindResult.DISPLAY_ID_MISMATCH;
+            case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW:
+                // Check with the window manager to make sure this client actually
+                // has a window with focus.  If not, reject.  This is thread safe
+                // because if the focus changes some time before or after, the
+                // next client receiving focus that has any interest in input will
+                // be calling through here after that change happens.
+                if (DEBUG) {
+                    Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+                            + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+                }
+                return InputBindResult.NOT_IME_TARGET_WINDOW;
+            case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID:
+                return InputBindResult.INVALID_DISPLAY_ID;
         }
 
         if (mUserSwitchHandlerTask != null) {
@@ -3561,8 +3600,7 @@
             if (cs == null) {
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
-            if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
-                    cs.selfReportedDisplayId)) {
+            if (!isImeClientFocused(mCurFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
@@ -3614,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();
         }
     }
 
@@ -3631,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();
     }
 
     /**
@@ -3679,8 +3718,7 @@
             if (!calledFromValidUserLocked()) {
                 return;
             }
-            executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO(
-                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
+            showInputMethodAndSubtypeEnabler(inputMethodId);
         }
     }
 
@@ -3890,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
@@ -4104,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) {
@@ -4155,22 +4193,17 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
-    void setEnabledSessionInHandlerThread(SessionState session) {
+    @GuardedBy("ImfLock.class")
+    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);
             }
         }
     }
@@ -4203,69 +4236,8 @@
                 mMenuController.showInputMethodMenu(showAuxSubtypes, displayId);
                 return true;
 
-            case MSG_SHOW_IM_SUBTYPE_ENABLER:
-                showInputMethodAndSubtypeEnabler((String)msg.obj);
-                return true;
-
-            case MSG_SHOW_IM_CONFIG:
-                showConfigureInputMethods();
-                return true;
-
             // ---------------------------------------------------------
 
-            case MSG_UNBIND_INPUT:
-                try {
-                    ((IInputMethod)msg.obj).unbindInput();
-                } catch (RemoteException e) {
-                    // There is nothing interesting about the method dying.
-                }
-                return true;
-            case MSG_BIND_INPUT:
-                args = (SomeArgs)msg.obj;
-                try {
-                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
-                } catch (RemoteException e) {
-                }
-                args.recycle();
-                return true;
-            case MSG_SHOW_SOFT_INPUT:
-                args = (SomeArgs) msg.obj;
-                try {
-                    final @SoftInputShowHideReason int reason = msg.arg2;
-                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
-                            + args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: "
-                            + InputMethodDebug.softInputDisplayReasonToString(reason));
-                    final IBinder token = (IBinder) args.arg3;
-                    ((IInputMethod) args.arg1).showSoftInput(
-                            token, msg.arg1 /* flags */, (ResultReceiver) args.arg2);
-                    final IBinder requestToken;
-                    synchronized (ImfLock.class) {
-                        requestToken = mShowRequestWindowMap.get(token);
-                        onShowHideSoftInputRequested(true /* show */, requestToken, reason);
-                    }
-                } catch (RemoteException e) {
-                }
-                args.recycle();
-                return true;
-            case MSG_HIDE_SOFT_INPUT:
-                args = (SomeArgs) msg.obj;
-                try {
-                    final @SoftInputShowHideReason int reason = msg.arg1;
-                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
-                            + args.arg3 + ", " + args.arg2 + ") for reason: "
-                            + InputMethodDebug.softInputDisplayReasonToString(reason));
-                    final IBinder token = (IBinder) args.arg3;
-                    ((IInputMethod)args.arg1).hideSoftInput(
-                            token, 0 /* flags */, (ResultReceiver) args.arg2);
-                    final IBinder requestToken;
-                    synchronized (ImfLock.class) {
-                        requestToken = mHideRequestWindowMap.get(token);
-                        onShowHideSoftInputRequested(false /* show */, requestToken, reason);
-                    }
-                } catch (RemoteException e) {
-                }
-                args.recycle();
-                return true;
             case MSG_HIDE_CURRENT_INPUT_METHOD:
                 synchronized (ImfLock.class) {
                     final @SoftInputShowHideReason int reason = (int) msg.obj;
@@ -4273,40 +4245,6 @@
 
                 }
                 return true;
-            case MSG_INITIALIZE_IME:
-                args = (SomeArgs)msg.obj;
-                try {
-                    if (DEBUG) {
-                        synchronized (ImfLock.class) {
-                            Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: "
-                                    + mCurTokenDisplayId);
-                        }
-                    }
-                    final IBinder token = (IBinder) args.arg2;
-                    ((IInputMethod) args.arg1).initializeInternal(token,
-                            new InputMethodPrivilegedOperationsImpl(this, token),
-                            msg.arg1, (boolean) args.arg3);
-                } catch (RemoteException e) {
-                }
-                args.recycle();
-                return true;
-            case MSG_CREATE_SESSION: {
-                args = (SomeArgs)msg.obj;
-                IInputMethod method = (IInputMethod)args.arg1;
-                InputChannel channel = (InputChannel)args.arg2;
-                try {
-                    method.createSession(channel, (IInputSessionCallback)args.arg3);
-                } catch (RemoteException e) {
-                } finally {
-                    // Dispose the channel if the input method is not local to this process
-                    // because the remote proxy will get its own copy when unparceled.
-                    if (channel != null && Binder.isProxy(method)) {
-                        channel.dispose();
-                    }
-                }
-                args.recycle();
-                return true;
-            }
             case MSG_REMOVE_IME_SURFACE: {
                 synchronized (ImfLock.class) {
                     try {
@@ -4338,25 +4276,6 @@
             }
             // ---------------------------------------------------------
 
-            case MSG_START_INPUT: {
-                final boolean restarting = msg.arg2 != 0;
-                args = (SomeArgs) msg.obj;
-                final IBinder startInputToken = (IBinder) args.arg1;
-                final SessionState session = (SessionState) args.arg2;
-                final IInputContext inputContext = (IInputContext) args.arg3;
-                final EditorInfo editorInfo = (EditorInfo) args.arg4;
-                try {
-                    setEnabledSessionInHandlerThread(session);
-                    session.method.startInput(startInputToken, inputContext, editorInfo,
-                            restarting);
-                } catch (RemoteException e) {
-                }
-                args.recycle();
-                return true;
-            }
-
-            // ---------------------------------------------------------
-
             case MSG_UNBIND_CLIENT:
                 try {
                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
@@ -4430,23 +4349,6 @@
             }
 
             // ---------------------------------------------------------------
-            case MSG_INLINE_SUGGESTIONS_REQUEST: {
-                args = (SomeArgs) msg.obj;
-                final InlineSuggestionsRequestInfo requestInfo =
-                        (InlineSuggestionsRequestInfo) args.arg2;
-                final IInlineSuggestionsRequestCallback callback =
-                        (IInlineSuggestionsRequestCallback) args.arg3;
-                try {
-                    ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(requestInfo,
-                            callback);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
-                }
-                args.recycle();
-                return true;
-            }
-
-            // ---------------------------------------------------------------
             case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: {
                 if (mAudioManagerInternal == null) {
                     mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
@@ -4456,13 +4358,6 @@
                 }
                 return true;
             }
-            case MSG_START_HANDWRITING:
-                try {
-                    (((IInputMethod) msg.obj)).canStartStylusHandwriting(msg.arg1);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
-                }
-                return true;
         }
         return false;
     }
@@ -4475,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);
         }
     }
 
@@ -4505,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")
@@ -4741,14 +4632,6 @@
         mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
     }
 
-    private void showConfigureInputMethods() {
-        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
-    }
-
     // ----------------------------------------------------------------------
 
     /**
@@ -5077,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();
         }
     }
 
@@ -5150,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));
             }
         }
     }
@@ -5221,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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 682a27a..8f05130 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,9 @@
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_ALL;
@@ -117,6 +120,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -642,12 +646,9 @@
 
     private void showEncryptionNotificationForProfile(UserHandle user) {
         Resources r = mContext.getResources();
-        CharSequence title = r.getText(
-                com.android.internal.R.string.profile_encrypted_title);
-        CharSequence message = r.getText(
-                com.android.internal.R.string.profile_encrypted_message);
-        CharSequence detail = r.getText(
-                com.android.internal.R.string.profile_encrypted_detail);
+        CharSequence title = getEncryptionNotificationTitle();
+        CharSequence message = getEncryptionNotificationMessage();
+        CharSequence detail = getEncryptionNotificationDetail();
 
         final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
         final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
@@ -663,6 +664,24 @@
         showEncryptionNotification(user, title, message, detail, intent);
     }
 
+    private String getEncryptionNotificationTitle() {
+        return mInjector.getDevicePolicyManager().getString(
+                PROFILE_ENCRYPTED_TITLE,
+                () -> mContext.getString(R.string.profile_encrypted_title));
+    }
+
+    private String getEncryptionNotificationDetail() {
+        return mInjector.getDevicePolicyManager().getString(
+                PROFILE_ENCRYPTED_DETAIL,
+                () -> mContext.getString(R.string.profile_encrypted_detail));
+    }
+
+    private String getEncryptionNotificationMessage() {
+        return mInjector.getDevicePolicyManager().getString(
+                PROFILE_ENCRYPTED_MESSAGE,
+                () -> mContext.getString(R.string.profile_encrypted_message));
+    }
+
     private void showEncryptionNotification(UserHandle user, CharSequence title,
             CharSequence message, CharSequence detail, PendingIntent intent) {
         if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier());
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/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index ffc1aed..91de9e5 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -344,10 +344,19 @@
     }
 
     private void addActiveRoute(BluetoothRouteInfo btRoute) {
+        if (btRoute == null) {
+            if (DEBUG) {
+                Log.d(TAG, " btRoute is null");
+            }
+            return;
+        }
         if (DEBUG) {
             Log.d(TAG, "Adding active route: " + btRoute.route);
         }
-        if (btRoute == null || mActiveRoutes.contains(btRoute)) {
+        if (mActiveRoutes.contains(btRoute)) {
+            if (DEBUG) {
+                Log.d(TAG, " btRoute is already added.");
+            }
             return;
         }
         setRouteConnectionState(btRoute, STATE_CONNECTED);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index a8383b6..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;
@@ -63,7 +62,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkIdentity.OEM_NONE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -131,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;
@@ -1011,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,
@@ -1215,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();
@@ -1237,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;
         }
     };
 
@@ -1454,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) {
@@ -1498,13 +1493,11 @@
         for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
             final int subId = mSubIdToSubscriberId.keyAt(i);
             final String subscriberId = mSubIdToSubscriberId.valueAt(i);
-            final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
-                    TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true,
-                    true, OEM_NONE);
-            /* While OEM_NONE indicates "any non OEM managed network", OEM_NONE is meant to be a
-             * placeholder value here. The probeIdent is matched against a NetworkTemplate which
-             * should have its OEM managed value set to OEM_MANAGED_ALL, which will cause the
-             * template to match probeIdent without regard to OEM managed status. */
+            final NetworkIdentity probeIdent = new NetworkIdentity.Builder()
+                    .setType(TYPE_MOBILE)
+                    .setSubscriberId(subscriberId)
+                    .setMetered(true)
+                    .setDefaultNetwork(true).build();
             if (template.matches(probeIdent)) {
                 return subId;
             }
@@ -1737,9 +1730,11 @@
 
         // find and update the carrier NetworkPolicy for this subscriber id
         boolean policyUpdated = false;
-        final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true,
-                OEM_NONE);
+        final NetworkIdentity probeIdent = new NetworkIdentity.Builder()
+                .setType(TYPE_MOBILE)
+                .setSubscriberId(subscriberId)
+                .setMetered(true)
+                .setDefaultNetwork(true).build();
         for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
             final NetworkTemplate template = mNetworkPolicy.keyAt(i);
             if (template.matches(probeIdent)) {
@@ -1967,10 +1962,11 @@
                 for (int i = 0; i < mSubIdToSubscriberId.size(); i++) {
                     final int subId = mSubIdToSubscriberId.keyAt(i);
                     final String subscriberId = mSubIdToSubscriberId.valueAt(i);
-
-                    final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
-                            TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true,
-                            true, OEM_NONE);
+                    final NetworkIdentity probeIdent = new NetworkIdentity.Builder()
+                            .setType(TYPE_MOBILE)
+                            .setSubscriberId(subscriberId)
+                            .setMetered(true)
+                            .setDefaultNetwork(true).build();
                     // Template is matched when subscriber id matches.
                     if (template.matches(probeIdent)) {
                         matchingSubIds.add(subId);
@@ -2074,11 +2070,9 @@
         for (final NetworkStateSnapshot snapshot : snapshots) {
             mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot));
 
-            // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype
-            // in the object created here is never used and its value doesn't matter, so use
-            // NETWORK_TYPE_UNKNOWN.
-            final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
-                    true, TelephonyManager.NETWORK_TYPE_UNKNOWN /* subType */);
+            // Policies matched by NPMS only match by subscriber ID or by network ID.
+            final NetworkIdentity ident = new NetworkIdentity.Builder()
+                    .setNetworkStateSnapshot(snapshot).setDefaultNetwork(true).build();
             identified.put(snapshot, ident);
         }
 
@@ -2275,9 +2269,11 @@
     @GuardedBy("mNetworkPoliciesSecondLock")
     private boolean ensureActiveCarrierPolicyAL(int subId, String subscriberId) {
         // Poke around to see if we already have a policy
-        final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true,
-                OEM_NONE);
+        final NetworkIdentity probeIdent = new NetworkIdentity.Builder()
+                .setType(TYPE_MOBILE)
+                .setSubscriberId(subscriberId)
+                .setMetered(true)
+                .setDefaultNetwork(true).build();
         for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
             final NetworkTemplate template = mNetworkPolicy.keyAt(i);
             if (template.matches(probeIdent)) {
@@ -2687,7 +2683,7 @@
         final List<WifiConfiguration> configs = wm.getConfiguredNetworks();
         for (int i = 0; i < configs.size(); ++i) {
             final WifiConfiguration config = configs.get(i);
-            for (String key : config.getAllPersistableNetworkKeys()) {
+            for (String key : config.getAllNetworkKeys()) {
                 final Boolean metered = wifiNetworkKeys.get(key);
                 if (metered != null) {
                     Slog.d(TAG, "Found network " + key + "; upgrading metered hint");
@@ -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/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 5e333da..05f000c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -101,6 +101,8 @@
 
     @VisibleForTesting
     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000;
+    @VisibleForTesting
+    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 50000;
 
     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
@@ -254,6 +256,7 @@
                                 }
                             }
                             boolean skipWarningLogged = false;
+                            boolean skipGroupWarningLogged = false;
                             boolean hasSAWPermission = false;
                             if (upgradeForBubbles && uid != UNKNOWN_UID) {
                                 hasSAWPermission = mAppOps.noteOpNoThrow(
@@ -303,6 +306,14 @@
                                 String tagName = parser.getName();
                                 // Channel groups
                                 if (TAG_GROUP.equals(tagName)) {
+                                    if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
+                                        if (!skipGroupWarningLogged) {
+                                            Slog.w(TAG, "Skipping further groups for " + r.pkg
+                                                    + "; app has too many");
+                                            skipGroupWarningLogged = true;
+                                        }
+                                        continue;
+                                    }
                                     String id = parser.getAttributeValue(null, ATT_ID);
                                     CharSequence groupName = parser.getAttributeValue(null,
                                             ATT_NAME);
@@ -867,6 +878,9 @@
             }
             if (fromTargetApp) {
                 group.setBlocked(false);
+                if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
+                    throw new IllegalStateException("Limit exceed; cannot create more groups");
+                }
             }
             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
             if (oldGroup != null) {
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index c9e564a..8e944b7 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -37,13 +37,14 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10
- * seconds without a transaction.
+ * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds
+ * without a transaction.
  **/
 class IdmapDaemon {
     // The amount of time in milliseconds to wait after a transaction to the idmap service is made
@@ -67,11 +68,14 @@
      * to the service is open.
      **/
     private class Connection implements AutoCloseable {
+        @Nullable
+        private final IIdmap2 mIdmap2;
         private boolean mOpened = true;
 
-        private Connection() {
+        private Connection(IIdmap2 idmap2) {
             synchronized (mIdmapToken) {
                 mOpenedCount.incrementAndGet();
+                mIdmap2 = idmap2;
             }
         }
 
@@ -102,6 +106,11 @@
                 }, mIdmapToken, SERVICE_TIMEOUT_MS);
             }
         }
+
+        @Nullable
+        public IIdmap2 getIdmap2() {
+            return mIdmap2;
+        }
     }
 
     static IdmapDaemon getInstance() {
@@ -115,14 +124,29 @@
             @Nullable String overlayName, int policies, boolean enforce, int userId)
             throws TimeoutException, RemoteException {
         try (Connection c = connect()) {
-            return mService.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for createIdmap(\"" + targetPath
+                        + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", "
+                        + enforce + ", " + userId + ")");
+                return null;
+            }
+
+            return idmap2.createIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
                     policies, enforce, userId);
         }
     }
 
     boolean removeIdmap(String overlayPath, int userId) throws TimeoutException, RemoteException {
         try (Connection c = connect()) {
-            return mService.removeIdmap(overlayPath, userId);
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for removeIdmap(\"" + overlayPath
+                        + "\", " + userId + ")");
+                return false;
+            }
+
+            return idmap2.removeIdmap(overlayPath, userId);
         }
     }
 
@@ -130,14 +154,29 @@
             @Nullable String overlayName, int policies, boolean enforce, int userId)
             throws Exception {
         try (Connection c = connect()) {
-            return mService.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for verifyIdmap(\"" + targetPath
+                        + "\", \"" + overlayPath + "\", \"" + overlayName + "\", " + policies + ", "
+                        + enforce + ", " + userId + ")");
+                return false;
+            }
+
+            return idmap2.verifyIdmap(targetPath, overlayPath, TextUtils.emptyIfNull(overlayName),
                     policies, enforce, userId);
         }
     }
 
     boolean idmapExists(String overlayPath, int userId) {
         try (Connection c = connect()) {
-            return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for idmapExists(\"" + overlayPath
+                        + "\", " + userId + ")");
+                return false;
+            }
+
+            return new File(idmap2.getIdmapPath(overlayPath, userId)).isFile();
         } catch (Exception e) {
             Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
             return false;
@@ -146,7 +185,13 @@
 
     FabricatedOverlayInfo createFabricatedOverlay(@NonNull FabricatedOverlayInternal overlay) {
         try (Connection c = connect()) {
-            return mService.createFabricatedOverlay(overlay);
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for createFabricatedOverlay()");
+                return null;
+            }
+
+            return idmap2.createFabricatedOverlay(overlay);
         } catch (Exception e) {
             Slog.wtf(TAG, "failed to fabricate overlay " + overlay, e);
             return null;
@@ -155,7 +200,14 @@
 
     boolean deleteFabricatedOverlay(@NonNull String path) {
         try (Connection c = connect()) {
-            return mService.deleteFabricatedOverlay(path);
+            final IIdmap2 idmap2 = c.getIdmap2();
+            if (idmap2 == null) {
+                Slog.w(TAG, "idmap2d service is not ready for deleteFabricatedOverlay(\"" + path
+                        + "\")");
+                return false;
+            }
+
+            return idmap2.deleteFabricatedOverlay(path);
         } catch (Exception e) {
             Slog.wtf(TAG, "failed to delete fabricated overlay '" + path + "'", e);
             return false;
@@ -164,10 +216,18 @@
 
     synchronized List<FabricatedOverlayInfo> getFabricatedOverlayInfos() {
         final ArrayList<FabricatedOverlayInfo> allInfos = new ArrayList<>();
-        try (Connection c = connect()) {
-            mService.acquireFabricatedOverlayIterator();
+        Connection c = null;
+        try {
+            c = connect();
+            final IIdmap2 service = c.getIdmap2();
+            if (service == null) {
+                Slog.w(TAG, "idmap2d service is not ready for getFabricatedOverlayInfos()");
+                return Collections.emptyList();
+            }
+
+            service.acquireFabricatedOverlayIterator();
             List<FabricatedOverlayInfo> infos;
-            while (!(infos = mService.nextFabricatedOverlayInfos()).isEmpty()) {
+            while (!(infos = service.nextFabricatedOverlayInfos()).isEmpty()) {
                 allInfos.addAll(infos);
             }
             return allInfos;
@@ -175,17 +235,26 @@
             Slog.wtf(TAG, "failed to get all fabricated overlays", e);
         } finally {
             try {
-                mService.releaseFabricatedOverlayIterator();
+                if (c.getIdmap2() != null) {
+                    c.getIdmap2().releaseFabricatedOverlayIterator();
+                }
             } catch (RemoteException e) {
                 // ignore
             }
+            c.close();
         }
         return allInfos;
     }
 
     String dumpIdmap(@NonNull String overlayPath) {
         try (Connection c = connect()) {
-            String dump = mService.dumpIdmap(overlayPath);
+            final IIdmap2 service = c.getIdmap2();
+            if (service == null) {
+                final String dumpText = "idmap2d service is not ready for dumpIdmap()";
+                Slog.w(TAG, dumpText);
+                return dumpText;
+            }
+            String dump = service.dumpIdmap(overlayPath);
             return TextUtils.nullIfEmpty(dump);
         } catch (Exception e) {
             Slog.wtf(TAG, "failed to dump idmap", e);
@@ -193,8 +262,16 @@
         }
     }
 
+    @Nullable
     private IBinder getIdmapService() throws TimeoutException, RemoteException {
-        SystemService.start(IDMAP_DAEMON);
+        try {
+            SystemService.start(IDMAP_DAEMON);
+        } catch (RuntimeException e) {
+            if (e.getMessage().contains("failed to set system property")) {
+                Slog.w(TAG, "Failed to enable idmap2 daemon", e);
+                return null;
+            }
+        }
 
         final long endMillis = SystemClock.elapsedRealtime() + SERVICE_CONNECT_TIMEOUT_MS;
         while (SystemClock.elapsedRealtime() <= endMillis) {
@@ -226,17 +303,23 @@
         }
     }
 
+    @NonNull
     private Connection connect() throws TimeoutException, RemoteException {
         synchronized (mIdmapToken) {
             FgThread.getHandler().removeCallbacksAndMessages(mIdmapToken);
             if (mService != null) {
                 // Not enough time has passed to stop the idmap service. Reuse the existing
                 // interface.
-                return new Connection();
+                return new Connection(mService);
             }
 
-            mService = IIdmap2.Stub.asInterface(getIdmapService());
-            return new Connection();
+            IBinder binder = getIdmapService();
+            if (binder == null) {
+                return new Connection(null);
+            }
+
+            mService = IIdmap2.Stub.asInterface(binder);
+            return new Connection(mService);
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 6f10a6b..2e9ad50 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -45,6 +45,7 @@
 import android.sysprop.ApexProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.PrintWriterPrinter;
 import android.util.Singleton;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -1164,6 +1165,10 @@
                 ipw.println("Path: " + pi.applicationInfo.sourceDir);
                 ipw.println("IsActive: " + isActive(pi));
                 ipw.println("IsFactory: " + isFactory(pi));
+                ipw.println("ApplicationInfo: ");
+                ipw.increaseIndent();
+                pi.applicationInfo.dump(new PrintWriterPrinter(ipw), "");
+                ipw.decreaseIndent();
                 ipw.decreaseIndent();
             }
             ipw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index a66af3c..4b999e9 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -24,7 +24,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackageManager;
-import com.android.server.pm.pkg.SELinuxUtil;
 import android.content.pm.UserInfo;
 import android.os.CreateAppDataArgs;
 import android.os.Environment;
@@ -35,6 +34,9 @@
 import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
 import android.os.storage.VolumeInfo;
+import android.security.AndroidKeyStoreMaintenance;
+import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -46,6 +48,7 @@
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.pkg.SELinuxUtil;
 
 import dalvik.system.VMRuntime;
 
@@ -156,8 +159,7 @@
      * <ul>
      * <li>If previousAppId < 0, app data will be migrated to the new app ID
      * <li>If previousAppId == 0, no migration will happen and data will be wiped and recreated
-     * <li>If previousAppId > 0, it will migrate all data owned by previousAppId
-     *     to the new app ID
+     * <li>If previousAppId > 0, app data owned by previousAppId will be migrated to the new app ID
      * </ul>
      */
     private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
@@ -476,13 +478,9 @@
             } else if (!ps.getInstalled(userId)) {
                 throw new PackageManagerException(
                         "Package " + packageName + " not installed for user " + userId);
-            } else if (ps.getPkg() == null) {
-                throw new PackageManagerException("Package " + packageName + " is not parsed yet");
-            } else {
-                if (!shouldHaveAppStorage(ps.getPkg())) {
-                    throw new PackageManagerException(
-                            "Package " + packageName + " shouldn't have storage");
-                }
+            } else if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) {
+                throw new PackageManagerException(
+                        "Package " + packageName + " shouldn't have storage");
             }
         }
     }
@@ -549,6 +547,22 @@
         return prepareAppDataFuture;
     }
 
+    public void migrateKeyStoreData(int previousAppId, int appId) {
+        for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) {
+            int srcUid = UserHandle.getUid(userId, previousAppId);
+            int destUid = UserHandle.getUid(userId, appId);
+            final KeyDescriptor[] keys = AndroidKeyStoreMaintenance.listEntries(Domain.APP, srcUid);
+            if (keys == null) continue;
+            for (final KeyDescriptor key : keys) {
+                KeyDescriptor dest = new KeyDescriptor();
+                dest.domain = Domain.APP;
+                dest.nspace = destUid;
+                dest.alias = key.alias;
+                AndroidKeyStoreMaintenance.migrateKeyNamespace(key, dest);
+            }
+        }
+    }
+
     void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) {
         if (pkg == null) {
             return;
@@ -633,4 +647,18 @@
                 pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
         return noAppDataProp == null || !noAppDataProp.getBoolean();
     }
+
+    /**
+     * Remove entries from the keystore daemon. Will only remove if the {@code appId} is valid.
+     */
+    public void clearKeystoreData(int userId, int appId) {
+        if (appId < 0) {
+            return;
+        }
+
+        for (int realUserId : mPm.resolveUserIds(userId)) {
+            AndroidKeyStoreMaintenance.clearNamespace(
+                    Domain.APP, UserHandle.getUid(realUserId, appId));
+        }
+    }
 }
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 2aa0e01..cca1b97 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
 import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
+import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
@@ -92,14 +93,6 @@
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
-import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
-import com.android.server.pm.pkg.component.ParsedActivity;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
-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.ParsedService;
-import com.android.server.pm.pkg.PackageUserStateUtils;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -142,6 +135,14 @@
 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.pkg.component.ParsedInstrumentation;
+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.ParsedService;
+import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.verify.domain.DomainVerificationUtils;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -350,7 +351,6 @@
     private final CompilerStats mCompilerStats;
     private final BackgroundDexOptService mBackgroundDexOptService;
     private final PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
-    private final ProtectedPackages mProtectedPackages;
 
     // PackageManagerService attributes that are primitives are referenced through the
     // pms object directly.  Primitives are the only attributes so referenced.
@@ -402,7 +402,6 @@
         mCompilerStats = args.service.mCompilerStats;
         mBackgroundDexOptService = args.service.mBackgroundDexOptService;
         mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
-        mProtectedPackages = args.service.mProtectedPackages;
 
         // Used to reference PMS attributes that are primitives and which are not
         // updated under control of the PMS lock.
@@ -824,7 +823,7 @@
         }
         if (resolveComponentName().equals(component)) {
             return PackageInfoWithoutStateUtils.generateDelegateActivityInfo(mResolveActivity,
-                    flags, PackageUserState.DEFAULT, userId);
+                    flags, PackageUserStateInternal.DEFAULT, userId);
         }
         return null;
     }
@@ -1548,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
@@ -3549,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);
             }
@@ -4012,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) {
@@ -4619,8 +4618,24 @@
             }
         }
         if (!checkedGrants) {
-            enforceCrossUserPermission(callingUid, userId, false, false, "resolveContentProvider");
+            boolean enforceCrossUser = true;
+
+            if (isAuthorityRedirectedForCloneProfile(name)) {
+                final UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+
+                UserInfo userInfo = umInternal.getUserInfo(UserHandle.getUserId(callingUid));
+                if (userInfo != null && userInfo.isCloneProfile()
+                        && userInfo.profileGroupId == userId) {
+                    enforceCrossUser = false;
+                }
+            }
+
+            if (enforceCrossUser) {
+                enforceCrossUserPermission(callingUid, userId, false, false,
+                        "resolveContentProvider");
+            }
         }
+
         if (providerInfo == null) {
             return 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/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 9a80a4e..48689a8 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -485,8 +485,7 @@
                 mAppDataHelper.destroyAppDataLIF(pkg, nextUserId,
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
             }
-            PackageManagerService.removeKeystoreDataIfNeeded(mUserManagerInternal, nextUserId,
-                    ps.getAppId());
+            mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId());
             preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
                     nextUserId);
             mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
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 6a5d76b..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);
@@ -2245,6 +2239,17 @@
             if (reconciledPkg.mScanResult.needsNewAppId()) {
                 // Only set previousAppId if the app is migrating out of shared UID
                 previousAppId = reconciledPkg.mScanResult.mPreviousAppId;
+
+                if (pkg.shouldInheritKeyStoreKeys()) {
+                    // Migrate keystore data
+                    mAppDataHelper.migrateKeyStoreData(
+                            previousAppId, reconciledPkg.mPkgSetting.getAppId());
+                }
+
+                if (reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId == previousAppId) {
+                    // If the previous app ID is removed, clear the keys
+                    mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, previousAppId);
+                }
             }
             mAppDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
             if (reconciledPkg.mPrepareResult.mClearCodeCache) {
@@ -3401,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);
@@ -3414,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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1f10d77..ccc375f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
@@ -29,6 +31,7 @@
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.Intent;
@@ -1312,7 +1315,7 @@
             mPackageName = packageName;
             if (showNotification) {
                 mNotification = buildSuccessNotification(mContext,
-                        mContext.getResources().getString(R.string.package_deleted_device_owner),
+                        getDeviceOwnerDeletedPackageMsg(),
                         packageName,
                         userId);
             } else {
@@ -1320,6 +1323,12 @@
             }
         }
 
+        private String getDeviceOwnerDeletedPackageMsg() {
+            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+            return dpm.getString(PACKAGE_DELETED_BY_DO,
+                    () -> mContext.getString(R.string.package_updated_device_owner));
+        }
+
         @Override
         public void onUserActionRequired(Intent intent) {
             if (mTarget == null) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e0f1b0b..d9ade96 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -17,6 +17,8 @@
 package com.android.server.pm;
 
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO;
 import static android.content.pm.DataLoaderType.INCREMENTAL;
 import static android.content.pm.DataLoaderType.STREAMING;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
@@ -56,6 +58,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -91,7 +94,6 @@
 import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.graphics.Bitmap;
@@ -156,6 +158,7 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
@@ -4336,9 +4339,7 @@
         if (INSTALL_SUCCEEDED == returnCode && showNotification) {
             boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
             Notification notification = PackageInstallerService.buildSuccessNotification(context,
-                    context.getResources()
-                            .getString(update ? R.string.package_updated_device_owner :
-                                    R.string.package_installed_device_owner),
+                    getDeviceOwnerInstalledPackageMsg(context, update),
                     basePackageName,
                     userId);
             if (notification != null) {
@@ -4370,6 +4371,15 @@
         }
     }
 
+    private static String getDeviceOwnerInstalledPackageMsg(Context context, boolean update) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        return update
+                ? dpm.getString(PACKAGE_UPDATED_BY_DO,
+                    () -> context.getString(R.string.package_updated_device_owner))
+                : dpm.getString(PACKAGE_INSTALLED_BY_DO,
+                    () -> context.getString(R.string.package_installed_device_owner));
+    }
+
     /**
      * This method doesn't change internal states and is safe to call outside the lock.
      */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 13f91e0d..e0d404a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -167,7 +167,6 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-import android.security.KeyStore;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -233,9 +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.SuspendParams;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
@@ -291,7 +288,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 /**
  * Keep track of all those APKs everywhere.
@@ -954,6 +950,7 @@
     final @Nullable String mRetailDemoPackage;
     final @Nullable String mOverlayConfigSignaturePackage;
     final @Nullable String mRecentsPackage;
+    final @Nullable String mAmbientContextDetectionPackage;
 
     @GuardedBy("mLock")
     private final PackageUsage mPackageUsage = new PackageUsage();
@@ -970,6 +967,7 @@
     private final PreferredActivityHelper mPreferredActivityHelper;
     private final ResolveIntentHelper mResolveIntentHelper;
     private final DexOptHelper mDexOptHelper;
+    private final SuspendPackageHelper mSuspendPackageHelper;
 
     /**
      * Invalidate the package info cache, which includes updating the cached computer.
@@ -1671,6 +1669,7 @@
         mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage;
         mRetailDemoPackage = testParams.retailDemoPackage;
         mRecentsPackage = testParams.recentsPackage;
+        mAmbientContextDetectionPackage = testParams.ambientContextDetectionPackage;
         mConfiguratorPackage = testParams.configuratorPackage;
         mAppPredictionServicePackage = testParams.appPredictionServicePackage;
         mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
@@ -1705,6 +1704,7 @@
         mPreferredActivityHelper = testParams.preferredActivityHelper;
         mResolveIntentHelper = testParams.resolveIntentHelper;
         mDexOptHelper = testParams.dexOptHelper;
+        mSuspendPackageHelper = testParams.suspendPackageHelper;
         mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
 
         invalidatePackageInfoCache();
@@ -1851,6 +1851,8 @@
         mPreferredActivityHelper = new PreferredActivityHelper(this);
         mResolveIntentHelper = new ResolveIntentHelper(this, mPreferredActivityHelper);
         mDexOptHelper = new DexOptHelper(this);
+        mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
+                mProtectedPackages);
 
         synchronized (mLock) {
             // Create the computer as soon as the state objects have been installed.  The
@@ -1995,6 +1997,7 @@
             mRetailDemoPackage = getRetailDemoPackageName();
             mOverlayConfigSignaturePackage = getOverlayConfigSignaturePackageName();
             mRecentsPackage = getRecentsPackageName();
+            mAmbientContextDetectionPackage = getAmbientContextDetectionPackageName();
 
             // Now that we know all of the shared libraries, update all clients to have
             // the correct library paths.
@@ -2682,7 +2685,7 @@
         return mComputer.getPackageUid(packageName, flags, userId);
     }
 
-    private int getPackageUidInternal(String packageName,
+    int getPackageUidInternal(String packageName,
             @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
         return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid);
     }
@@ -4294,51 +4297,6 @@
         info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) {
-        final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
-        final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
-        final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
-        final int[] userIds = new int[] {userId};
-        // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
-        // allow lists are the same.
-        for (int i = 0; i < pkgList.length; i++) {
-            final String pkgName = pkgList[i];
-            final int uid = uidList[i];
-            SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
-                    getPackageStateInternal(pkgName, Process.SYSTEM_UID),
-                    userIds, getPackageStates());
-            if (allowList == null) {
-                allowList = new SparseArray<>(0);
-            }
-            boolean merged = false;
-            for (int j = 0; j < allowListsToSend.size(); j++) {
-                if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
-                    pkgsToSend.get(j).add(pkgName);
-                    uidsToSend.get(j).add(uid);
-                    merged = true;
-                    break;
-                }
-            }
-            if (!merged) {
-                pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
-                uidsToSend.add(IntArray.wrap(new int[] {uid}));
-                allowListsToSend.add(allowList);
-            }
-        }
-
-        for (int i = 0; i < pkgsToSend.size(); i++) {
-            final Bundle extras = new Bundle(3);
-            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
-                    pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
-            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
-            final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
-                    ? null : allowListsToSend.get(i);
-            sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null,
-                    null, userIds, null, allowList, null);
-        }
-    }
-
     /**
      * Returns true if application is not found or there was an error. Otherwise it returns
      * the hidden state of the package for the given user.
@@ -4381,7 +4339,8 @@
                     + userId);
         }
         Objects.requireNonNull(packageNames, "packageNames cannot be null");
-        if (restrictionFlags != 0 && !isSuspendAllowedForUser(userId)) {
+        if (restrictionFlags != 0
+                && !mSuspendPackageHelper.isSuspendAllowedForUser(userId, callingUid)) {
             Slog.w(TAG, "Cannot restrict packages due to restrictions on user " + userId);
             return packageNames;
         }
@@ -4389,8 +4348,9 @@
         final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
         final IntArray changedUids = new IntArray(packageNames.length);
         final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
-        final boolean[] canRestrict = (restrictionFlags != 0) ? canSuspendPackageForUserInternal(
-                packageNames, userId) : null;
+        final boolean[] canRestrict = (restrictionFlags != 0)
+                ? mSuspendPackageHelper.canSuspendPackageForUser(packageNames, userId, callingUid)
+                : null;
 
         for (int i = 0; i < packageNames.length; i++) {
             final String packageName = packageNames[i];
@@ -4469,84 +4429,8 @@
         final int callingUid = Binder.getCallingUid();
         enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId,
                 "setPackagesSuspendedAsUser");
-
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return packageNames;
-        }
-        if (suspended && !isSuspendAllowedForUser(userId)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
-            return packageNames;
-        }
-
-        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
-        final IntArray changedUids = new IntArray(packageNames.length);
-        final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
-        final IntArray modifiedUids = new IntArray(packageNames.length);
-        final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
-        final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames,
-                userId) : null;
-
-        for (int i = 0; i < packageNames.length; i++) {
-            final String packageName = packageNames[i];
-            if (callingPackage.equals(packageName)) {
-                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
-                        + (suspended ? "" : "un") + "suspend itself. Ignoring");
-                unactionedPackages.add(packageName);
-                continue;
-            }
-            final PackageSetting pkgSetting;
-            synchronized (mLock) {
-                pkgSetting = mSettings.getPackageLPr(packageName);
-                if (pkgSetting == null
-                        || shouldFilterApplication(pkgSetting, callingUid, userId)) {
-                    Slog.w(TAG, "Could not find package setting for package: " + packageName
-                            + ". Skipping suspending/un-suspending.");
-                    unactionedPackages.add(packageName);
-                    continue;
-                }
-            }
-            if (canSuspend != null && !canSuspend[i]) {
-                unactionedPackages.add(packageName);
-                continue;
-            }
-            final boolean packageUnsuspended;
-            final boolean packageModified;
-            synchronized (mLock) {
-                if (suspended) {
-                    packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
-                            dialogInfo, appExtras, launcherExtras, userId);
-                } else {
-                    packageModified = pkgSetting.removeSuspension(callingPackage, userId);
-                }
-                packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
-            }
-            if (suspended || packageUnsuspended) {
-                changedPackagesList.add(packageName);
-                changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
-            }
-            if (packageModified) {
-                modifiedPackagesList.add(packageName);
-                modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
-            }
-        }
-
-        if (!changedPackagesList.isEmpty()) {
-            final String[] changedPackages = changedPackagesList.toArray(new String[0]);
-            sendPackagesSuspendedForUser(
-                    suspended ? Intent.ACTION_PACKAGES_SUSPENDED
-                              : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    changedPackages, changedUids.toArray(), userId);
-            sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
-            synchronized (mLock) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        }
-        // Send the suspension changed broadcast to ensure suspension state is not stale.
-        if (!modifiedPackagesList.isEmpty()) {
-            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
-                    modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
-        }
-        return unactionedPackages.toArray(new String[0]);
+        return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras,
+                launcherExtras, dialogInfo, callingPackage, userId, callingUid);
     }
 
     @Override
@@ -4556,56 +4440,8 @@
             throw new SecurityException("Calling package " + packageName
                     + " does not belong to calling uid " + callingUid);
         }
-        return getSuspendedPackageAppExtrasInternal(packageName, userId);
-    }
-
-    private Bundle getSuspendedPackageAppExtrasInternal(String packageName, int userId) {
-        final PackageStateInternal ps = getPackageStateInternal(packageName);
-        if (ps == null) {
-            return null;
-        }
-        final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId);
-        final Bundle allExtras = new Bundle();
-        if (pus.isSuspended()) {
-            for (int i = 0; i < pus.getSuspendParams().size(); i++) {
-                final SuspendParams params = pus.getSuspendParams().valueAt(i);
-                if (params != null && params.getAppExtras() != null) {
-                    allExtras.putAll(params.getAppExtras());
-                }
-            }
-        }
-        return (allExtras.size() > 0) ? allExtras : null;
-    }
-
-    private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
-            int userId) {
-        final String action = suspended
-                ? Intent.ACTION_MY_PACKAGE_SUSPENDED
-                : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
-        mHandler.post(() -> {
-            final IActivityManager am = ActivityManager.getService();
-            if (am == null) {
-                Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
-                        + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
-                return;
-            }
-            final int[] targetUserIds = new int[] {userId};
-            for (String packageName : affectedPackages) {
-                final Bundle appExtras = suspended
-                        ? getSuspendedPackageAppExtrasInternal(packageName, userId)
-                        : null;
-                final Bundle intentExtras;
-                if (appExtras != null) {
-                    intentExtras = new Bundle(1);
-                    intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
-                } else {
-                    intentExtras = null;
-                }
-                mHandler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
-                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
-                        targetUserIds, false, null, null));
-            }
-        });
+        return mSuspendPackageHelper.getSuspendedPackageAppExtras(
+                packageName, userId, callingUid);
     }
 
     @Override
@@ -4618,50 +4454,14 @@
         synchronized (mLock) {
             allPackages = mPackages.keySet().toArray(new String[mPackages.size()]);
         }
-        removeSuspensionsBySuspendingPackage(allPackages, suspendingPackage::equals, userId);
+        mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
+                allPackages, suspendingPackage::equals, userId);
     }
 
     private boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
         return mComputer.isSuspendingAnyPackages(suspendingPackage, userId);
     }
 
-    /**
-     * Removes any suspensions on given packages that were added by packages that pass the given
-     * predicate.
-     *
-     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
-     *
-     * @param packagesToChange The packages on which the suspension are to be removed.
-     * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
-     *                                   suspensions will be removed.
-     * @param userId The user for which the changes are taking place.
-     */
-    private void removeSuspensionsBySuspendingPackage(String[] packagesToChange,
-            Predicate<String> suspendingPackagePredicate, int userId) {
-        final List<String> unsuspendedPackages = new ArrayList<>();
-        final IntArray unsuspendedUids = new IntArray();
-        synchronized (mLock) {
-            for (String packageName : packagesToChange) {
-                final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
-                    ps.removeSuspension(suspendingPackagePredicate, userId);
-                    if (!ps.getUserStateOrDefault(userId).isSuspended()) {
-                        unsuspendedPackages.add(ps.getPackageName());
-                        unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
-                    }
-                }
-            }
-            scheduleWritePackageRestrictionsLocked(userId);
-        }
-        if (!unsuspendedPackages.isEmpty()) {
-            final String[] packageArray = unsuspendedPackages.toArray(
-                    new String[unsuspendedPackages.size()]);
-            sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
-            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    packageArray, unsuspendedUids.toArray(), userId);
-        }
-    }
-
     void removeAllDistractingPackageRestrictions(int userId) {
         final String[] allPackages = mComputer.getAllAvailablePackageNames();
         removeDistractingPackageRestrictions(allPackages, userId);
@@ -4698,24 +4498,6 @@
         }
     }
 
-    private boolean isCallerDeviceOrProfileOwner(int userId) {
-        final int callingUid = Binder.getCallingUid();
-        if (callingUid == Process.SYSTEM_UID) {
-            return true;
-        }
-        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
-        if (ownerPackage != null) {
-            return callingUid == getPackageUidInternal(ownerPackage, 0, userId, callingUid);
-        }
-        return false;
-    }
-
-    private boolean isSuspendAllowedForUser(int userId) {
-        return isCallerDeviceOrProfileOwner(userId)
-                || (!mUserManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
-                && !mUserManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
-    }
-
     @Override
     public String[] getUnsuspendablePackagesForUser(String[] packageNames, int userId) {
         Objects.requireNonNull(packageNames, "packageNames cannot be null");
@@ -4726,125 +4508,8 @@
             throw new SecurityException("Calling uid " + callingUid
                     + " cannot query getUnsuspendablePackagesForUser for user " + userId);
         }
-        if (!isSuspendAllowedForUser(userId)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
-            return packageNames;
-        }
-        final ArraySet<String> unactionablePackages = new ArraySet<>();
-        final boolean[] canSuspend = canSuspendPackageForUserInternal(packageNames, userId);
-        for (int i = 0; i < packageNames.length; i++) {
-            if (!canSuspend[i]) {
-                unactionablePackages.add(packageNames[i]);
-                continue;
-            }
-            synchronized (mLock) {
-                final PackageSetting ps = mSettings.getPackageLPr(packageNames[i]);
-                if (ps == null || shouldFilterApplication(ps, callingUid, userId)) {
-                    Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
-                    unactionablePackages.add(packageNames[i]);
-                }
-            }
-        }
-        return unactionablePackages.toArray(new String[unactionablePackages.size()]);
-    }
-
-    /**
-     * Returns an array of booleans, such that the ith boolean denotes whether the ith package can
-     * be suspended or not.
-     *
-     * @param packageNames  The package names to check suspendability for.
-     * @param userId The user to check in
-     * @return An array containing results of the checks
-     */
-    @NonNull
-    private boolean[] canSuspendPackageForUserInternal(@NonNull String[] packageNames, int userId) {
-        final boolean[] canSuspend = new boolean[packageNames.length];
-        final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId);
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            final String activeLauncherPackageName = getActiveLauncherPackageName(userId);
-            final String dialerPackageName = mDefaultAppProvider.getDefaultDialer(userId);
-            for (int i = 0; i < packageNames.length; i++) {
-                canSuspend[i] = false;
-                final String packageName = packageNames[i];
-
-                if (isPackageDeviceAdmin(packageName, userId)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": has an active device admin");
-                    continue;
-                }
-                if (packageName.equals(activeLauncherPackageName)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": contains the active launcher");
-                    continue;
-                }
-                if (packageName.equals(mRequiredInstallerPackage)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": required for package installation");
-                    continue;
-                }
-                if (packageName.equals(mRequiredUninstallerPackage)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": required for package uninstallation");
-                    continue;
-                }
-                if (packageName.equals(mRequiredVerifierPackage)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": required for package verification");
-                    continue;
-                }
-                if (packageName.equals(dialerPackageName)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": is the default dialer");
-                    continue;
-                }
-                if (packageName.equals(mRequiredPermissionControllerPackage)) {
-                    Slog.w(TAG, "Cannot suspend package \"" + packageName
-                            + "\": required for permissions management");
-                    continue;
-                }
-                synchronized (mLock) {
-                    if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
-                        Slog.w(TAG, "Cannot suspend package \"" + packageName
-                                + "\": protected package");
-                        continue;
-                    }
-                    if (!isCallerOwner && mSettings.getBlockUninstallLPr(userId, packageName)) {
-                        Slog.w(TAG, "Cannot suspend package \"" + packageName
-                                + "\": blocked by admin");
-                        continue;
-                    }
-
-                    AndroidPackage pkg = mPackages.get(packageName);
-                    if (pkg != null) {
-                        // Cannot suspend SDK libs as they are controlled by SDK manager.
-                        if (pkg.isSdkLibrary()) {
-                            Slog.w(TAG, "Cannot suspend package: " + packageName
-                                    + " providing SDK library: "
-                                    + pkg.getSdkLibName());
-                            continue;
-                        }
-                        // Cannot suspend static shared libs as they are considered
-                        // a part of the using app (emulating static linking). Also
-                        // static libs are installed always on internal storage.
-                        if (pkg.isStaticSharedLibrary()) {
-                            Slog.w(TAG, "Cannot suspend package: " + packageName
-                                    + " providing static shared library: "
-                                    + pkg.getStaticSharedLibName());
-                            continue;
-                        }
-                    }
-                }
-                if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
-                    Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
-                    continue;
-                }
-                canSuspend[i] = true;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-        return canSuspend;
+        return mSuspendPackageHelper.getUnsuspendablePackagesForUser(
+                packageNames, userId, callingUid);
     }
 
     @Override
@@ -5445,7 +5110,7 @@
                 FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
 
         final int appId = UserHandle.getAppId(pkg.getUid());
-        removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId);
+        mAppDataHelper.clearKeystoreData(userId, appId);
 
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
         StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
@@ -5518,30 +5183,6 @@
         }
     }
 
-    /**
-     * Remove entries from the keystore daemon. Will only remove it if the
-     * {@code appId} is valid.
-     */
-    static void removeKeystoreDataIfNeeded(UserManagerInternal um, @UserIdInt int userId,
-            @AppIdInt int appId) {
-        if (appId < 0) {
-            return;
-        }
-
-        final KeyStore keyStore = KeyStore.getInstance();
-        if (keyStore != null) {
-            if (userId == UserHandle.USER_ALL) {
-                for (final int individual : um.getUserIds()) {
-                    keyStore.clearUid(UserHandle.getUid(individual, appId));
-                }
-            } else {
-                keyStore.clearUid(UserHandle.getUid(userId, appId));
-            }
-        } else {
-            Slog.w(TAG, "Could not contact keystore to clear entries for app id " + appId);
-        }
-    }
-
     @Override
     public void deleteApplicationCacheFiles(final String packageName,
             final IPackageDataObserver observer) {
@@ -5980,6 +5621,11 @@
         return mPmInternal.getSetupWizardPackageName();
     }
 
+    public @Nullable String getAmbientContextDetectionPackageName() {
+        return ensureSystemPackageName(getPackageFromComponentString(
+                        R.string.config_defaultAmbientContextDetectionService));
+    }
+
     public String getIncidentReportApproverPackageName() {
         return ensureSystemPackageName(mContext.getString(
                 R.string.config_incidentReportApproverPackage));
@@ -7420,41 +7066,27 @@
 
         @Override
         public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
-            final PackageStateInternal packageState = getPackageStateInternal(packageName);
-            if (packageState == null) {
-                return null;
-            }
-            Bundle allExtras = new Bundle();
-            PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
-            if (userState.isSuspended()) {
-                for (int i = 0; i < userState.getSuspendParams().size(); i++) {
-                    final SuspendParams params = userState.getSuspendParams().valueAt(i);
-                    if (params != null && params.getLauncherExtras() != null) {
-                        allExtras.putAll(params.getLauncherExtras());
-                    }
-                }
-            }
-            return (allExtras.size() > 0) ? allExtras : null;
+            return mSuspendPackageHelper.getSuspendedPackageLauncherExtras(
+                    packageName, userId, Binder.getCallingUid());
         }
 
         @Override
         public boolean isPackageSuspended(String packageName, int userId) {
-            final PackageStateInternal packageState = getPackageStateInternal(packageName);
-            return packageState != null && packageState.getUserStateOrDefault(userId)
-                    .isSuspended();
+            return mSuspendPackageHelper.isPackageSuspended(
+                    packageName, userId, Binder.getCallingUid());
         }
 
         @Override
         public void removeAllNonSystemPackageSuspensions(int userId) {
             final String[] allPackages = mComputer.getAllAvailablePackageNames();
-            PackageManagerService.this.removeSuspensionsBySuspendingPackage(allPackages,
+            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages,
                     (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                     userId);
         }
 
         @Override
         public void removeNonSystemPackageSuspensions(String packageName, int userId) {
-            PackageManagerService.this.removeSuspensionsBySuspendingPackage(
+            mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(
                     new String[]{packageName},
                     (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
                     userId);
@@ -7480,46 +7112,15 @@
 
         @Override
         public String getSuspendingPackage(String suspendedPackage, int userId) {
-            final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage);
-            if (packageState == null) {
-                return  null;
-            }
-
-            final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
-            if (!userState.isSuspended()) {
-                return null;
-            }
-
-            String suspendingPackage = null;
-            for (int i = 0; i < userState.getSuspendParams().size(); i++) {
-                suspendingPackage = userState.getSuspendParams().keyAt(i);
-                if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
-                    return suspendingPackage;
-                }
-            }
-            return suspendingPackage;
+            return mSuspendPackageHelper.getSuspendingPackage(
+                    suspendedPackage, userId, Binder.getCallingUid());
         }
 
         @Override
         public SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
                 String suspendingPackage, int userId) {
-            final PackageStateInternal packageState = getPackageStateInternal(suspendedPackage);
-            if (packageState == null) {
-                return  null;
-            }
-
-            final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
-            if (!userState.isSuspended()) {
-                return null;
-            }
-
-            final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams();
-            if (suspendParamsMap == null) {
-                return null;
-            }
-
-            final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
-            return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
+            return mSuspendPackageHelper.getSuspendedDialogInfo(
+                    suspendedPackage, suspendingPackage, userId, Binder.getCallingUid());
         }
 
         @Override
@@ -9083,6 +8684,8 @@
                 return new String[] { mDefaultAppProvider.getDefaultBrowser(userId) };
             case PackageManagerInternal.PACKAGE_INSTALLER:
                 return mComputer.filterOnlySystemPackages(mRequiredInstallerPackage);
+            case PackageManagerInternal.PACKAGE_UNINSTALLER:
+                return mComputer.filterOnlySystemPackages(mRequiredUninstallerPackage);
             case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
                 return mComputer.filterOnlySystemPackages(mSetupWizardPackage);
             case PackageManagerInternal.PACKAGE_SYSTEM:
@@ -9098,6 +8701,8 @@
                 return mComputer.filterOnlySystemPackages(mConfiguratorPackage);
             case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
                 return mComputer.filterOnlySystemPackages(mIncidentReportApproverPackage);
+            case PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION:
+                return mComputer.filterOnlySystemPackages(mAmbientContextDetectionPackage);
             case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
                 return mComputer.filterOnlySystemPackages(mAppPredictionServicePackage);
             case PackageManagerInternal.PACKAGE_COMPANION:
@@ -9160,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;
@@ -9192,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/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index a1acc38..db60686 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -91,6 +91,7 @@
     public ViewCompiler viewCompiler;
     public @Nullable String retailDemoPackage;
     public @Nullable String recentsPackage;
+    public @Nullable String ambientContextDetectionPackage;
     public ComponentName resolveComponentName;
     public ArrayMap<String, AndroidPackage> packages;
     public boolean enableFreeCacheV2;
@@ -111,4 +112,5 @@
     public PreferredActivityHelper preferredActivityHelper;
     public ResolveIntentHelper resolveIntentHelper;
     public DexOptHelper dexOptHelper;
+    public SuspendPackageHelper suspendPackageHelper;
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index d8f0cc3..1d2b829 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -51,7 +51,6 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
-import com.android.server.pm.pkg.component.ParsedMainComponent;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Binder;
@@ -92,6 +91,7 @@
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ParsedMainComponent;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 
 import dalvik.system.VMRuntime;
@@ -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;
     }
@@ -560,8 +558,8 @@
 
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                        "Package " + packageName +
-                        " signatures do not match previously installed version; ignoring!");
+                        "Existing package " + packageName
+                                + " signatures do not match newer version; ignoring!");
             }
         }
         // Check for shared user signatures
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/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index b0e0340..7e898cb 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -29,7 +29,6 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
-import com.android.server.pm.pkg.component.ParsedInstrumentation;
 import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
 import android.util.Log;
@@ -43,6 +42,7 @@
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.component.ParsedInstrumentation;
 
 import java.io.File;
 import java.util.Collections;
@@ -330,8 +330,7 @@
         if (removedAppId != -1) {
             // A user ID was deleted here. Go through all users and remove it
             // from KeyStore.
-            mPm.removeKeystoreDataIfNeeded(
-                    mUserManagerInternal, UserHandle.USER_ALL, removedAppId);
+            mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, removedAppId);
         }
     }
 }
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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index bf7ef1b..15e64df 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -146,8 +146,10 @@
     private static final String ATTR_PERSON_IS_IMPORTANT = "is-important";
 
     private static final String NAME_CATEGORIES = "categories";
+    private static final String NAME_CAPABILITY = "capability";
 
     private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array";
+    private static final String TAG_MAP_XMLUTILS = "map";
     private static final String ATTR_NAME_XMLUTILS = "name";
 
     private static final String KEY_DYNAMIC = "dynamic";
@@ -1829,6 +1831,12 @@
             }
 
             ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+
+            final Map<String, Map<String, List<String>>> capabilityBindings =
+                    si.getCapabilityBindings();
+            if (capabilityBindings != null && !capabilityBindings.isEmpty()) {
+                XmlUtils.writeMapXml(si.getCapabilityBindings(), NAME_CAPABILITY, out);
+            }
         }
 
         out.endTag(null, TAG_SHORTCUT);
@@ -1961,6 +1969,7 @@
         int backupVersionCode;
         ArraySet<String> categories = null;
         ArrayList<Person> persons = new ArrayList<>();
+        Map<String, Map<String, List<String>>> capabilityBindings = null;
 
         id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
         activityComponent = ShortcutService.parseComponentNameAttribute(parser,
@@ -2029,6 +2038,13 @@
                         }
                     }
                     continue;
+                case TAG_MAP_XMLUTILS:
+                    if (NAME_CAPABILITY.equals(ShortcutService.parseStringAttribute(parser,
+                            ATTR_NAME_XMLUTILS))) {
+                        capabilityBindings = (Map<String, Map<String, List<String>>>)
+                                XmlUtils.readValueXml(parser, new String[1]);
+                    }
+                    continue;
             }
             throw ShortcutService.throwForInvalidTag(depth, tag);
         }
@@ -2064,7 +2080,7 @@
                 rank, extras, lastChangedTimestamp, flags,
                 iconResId, iconResName, bitmapPath, iconUri,
                 disabledReason, persons.toArray(new Person[persons.size()]), locusId,
-                splashScreenThemeResName);
+                splashScreenThemeResName, capabilityBindings);
     }
 
     private static Intent parseIntent(TypedXmlPullParser parser)
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index b86c50b..63f1f2d 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -459,7 +459,8 @@
                 disabledReason,
                 null /* persons */,
                 null /* locusId */,
-                splashScreenThemeResName);
+                splashScreenThemeResName,
+                null /* capabilityBindings */);
     }
 
     private static String parseCategory(ShortcutService service, AttributeSet attrs) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
new file mode 100644
index 0000000..f466ca7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -0,0 +1,611 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER;
+import static android.content.pm.PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER;
+import static android.content.pm.PackageManagerInternal.PACKAGE_UNINSTALLER;
+import static android.content.pm.PackageManagerInternal.PACKAGE_VERIFIER;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.content.Intent;
+import android.content.pm.PackageManagerInternal.KnownPackage;
+import android.content.pm.SuspendDialogInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
+import com.android.server.pm.pkg.SuspendParams;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+public final class SuspendPackageHelper {
+    // TODO(b/198166813): remove PMS dependency
+    private final PackageManagerService mPm;
+    private final PackageManagerServiceInjector mInjector;
+
+    private final BroadcastHelper mBroadcastHelper;
+    private final ProtectedPackages mProtectedPackages;
+
+    /**
+     * Constructor for {@link PackageManagerService}.
+     */
+    SuspendPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector,
+            BroadcastHelper broadcastHelper, ProtectedPackages protectedPackages) {
+        mPm = pm;
+        mInjector = injector;
+        mBroadcastHelper = broadcastHelper;
+        mProtectedPackages = protectedPackages;
+    }
+
+    /**
+     * Updates the package to the suspended or unsuspended state.
+     *
+     * @param packageNames The names of the packages to set the suspended status.
+     * @param suspended {@code true} to suspend packages, or {@code false} to unsuspend packages.
+     * @param appExtras An optional {@link PersistableBundle} that the suspending app can provide
+     *                  which will be shared with the apps being suspended. Ignored if
+     *                  {@code suspended} is false.
+     * @param launcherExtras An optional {@link PersistableBundle} that the suspending app can
+     *                       provide which will be shared with the launcher. Ignored if
+     *                       {@code suspended} is false.
+     * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
+     *                   should be shown to the user when they try to launch a suspended app.
+     *                   Ignored if {@code suspended} is false.
+     * @param callingPackage The caller's package name.
+     * @param userId The user where packages reside.
+     * @param callingUid The caller's uid.
+     * @return The names of failed packages.
+     */
+    @Nullable
+    String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
+            @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
+            @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage,
+            int userId, int callingUid) {
+        if (ArrayUtils.isEmpty(packageNames)) {
+            return packageNames;
+        }
+        if (suspended && !isSuspendAllowedForUser(userId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+            return packageNames;
+        }
+
+        final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
+        final IntArray changedUids = new IntArray(packageNames.length);
+        final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
+        final IntArray modifiedUids = new IntArray(packageNames.length);
+        final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
+        final boolean[] canSuspend =
+                suspended ? canSuspendPackageForUser(packageNames, userId, callingUid) : null;
+
+        for (int i = 0; i < packageNames.length; i++) {
+            final String packageName = packageNames[i];
+            if (callingPackage.equals(packageName)) {
+                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+                        + (suspended ? "" : "un") + "suspend itself. Ignoring");
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            final PackageSetting pkgSetting;
+            synchronized (mPm.mLock) {
+                pkgSetting = mPm.mSettings.getPackageLPr(packageName);
+                if (pkgSetting == null
+                        || mPm.shouldFilterApplication(pkgSetting, callingUid, userId)) {
+                    Slog.w(TAG, "Could not find package setting for package: " + packageName
+                            + ". Skipping suspending/un-suspending.");
+                    unactionedPackages.add(packageName);
+                    continue;
+                }
+            }
+            if (canSuspend != null && !canSuspend[i]) {
+                unactionedPackages.add(packageName);
+                continue;
+            }
+            final boolean packageUnsuspended;
+            final boolean packageModified;
+            synchronized (mPm.mLock) {
+                if (suspended) {
+                    packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
+                            dialogInfo, appExtras, launcherExtras, userId);
+                } else {
+                    packageModified = pkgSetting.removeSuspension(callingPackage, userId);
+                }
+                packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
+            }
+            if (suspended || packageUnsuspended) {
+                changedPackagesList.add(packageName);
+                changedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+            }
+            if (packageModified) {
+                modifiedPackagesList.add(packageName);
+                modifiedUids.add(UserHandle.getUid(userId, pkgSetting.getAppId()));
+            }
+        }
+
+        if (!changedPackagesList.isEmpty()) {
+            final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+            sendPackagesSuspendedForUser(
+                    suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+                            : Intent.ACTION_PACKAGES_UNSUSPENDED,
+                    changedPackages, changedUids.toArray(), userId);
+            sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
+            synchronized (mPm.mLock) {
+                mPm.scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+        // Send the suspension changed broadcast to ensure suspension state is not stale.
+        if (!modifiedPackagesList.isEmpty()) {
+            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+                    modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+        }
+        return unactionedPackages.toArray(new String[0]);
+    }
+
+    /**
+     * Returns the names in the {@code packageNames} which can not be suspended by the caller.
+     *
+     * @param packageNames The names of packages to check.
+     * @param userId The user where packages reside.
+     * @param callingUid The caller's uid.
+     * @return The names of packages which are Unsuspendable.
+     */
+    @NonNull
+    String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId,
+            int callingUid) {
+        if (!isSuspendAllowedForUser(userId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+            return packageNames;
+        }
+        final ArraySet<String> unactionablePackages = new ArraySet<>();
+        final boolean[] canSuspend = canSuspendPackageForUser(packageNames, userId, callingUid);
+        for (int i = 0; i < packageNames.length; i++) {
+            if (!canSuspend[i]) {
+                unactionablePackages.add(packageNames[i]);
+                continue;
+            }
+            synchronized (mPm.mLock) {
+                final PackageSetting ps = mPm.mSettings.getPackageLPr(packageNames[i]);
+                if (ps == null || mPm.shouldFilterApplication(ps, callingUid, userId)) {
+                    Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
+                    unactionablePackages.add(packageNames[i]);
+                }
+            }
+        }
+        return unactionablePackages.toArray(new String[unactionablePackages.size()]);
+    }
+
+    /**
+     * Returns the app extras of the given suspended package.
+     *
+     * @param packageName The suspended package name.
+     * @param userId The user where the package resides.
+     * @param callingUid The caller's uid.
+     * @return The app extras of the suspended package.
+     */
+    @Nullable
+    Bundle getSuspendedPackageAppExtras(@NonNull String packageName, int userId, int callingUid) {
+        final PackageStateInternal ps = mPm.getPackageStateInternal(packageName, callingUid);
+        if (ps == null) {
+            return null;
+        }
+        final PackageUserStateInternal pus = ps.getUserStateOrDefault(userId);
+        final Bundle allExtras = new Bundle();
+        if (pus.isSuspended()) {
+            for (int i = 0; i < pus.getSuspendParams().size(); i++) {
+                final SuspendParams params = pus.getSuspendParams().valueAt(i);
+                if (params != null && params.getAppExtras() != null) {
+                    allExtras.putAll(params.getAppExtras());
+                }
+            }
+        }
+        return (allExtras.size() > 0) ? allExtras : null;
+    }
+
+    /**
+     * Removes any suspensions on given packages that were added by packages that pass the given
+     * predicate.
+     *
+     * <p> Caller must flush package restrictions if it cares about immediate data consistency.
+     *
+     * @param packagesToChange The packages on which the suspension are to be removed.
+     * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
+     *                                   suspensions will be removed.
+     * @param userId The user for which the changes are taking place.
+     */
+    void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange,
+            @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
+        final List<String> unsuspendedPackages = new ArrayList<>();
+        final IntArray unsuspendedUids = new IntArray();
+        synchronized (mPm.mLock) {
+            for (String packageName : packagesToChange) {
+                final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+                if (ps != null && ps.getUserStateOrDefault(userId).isSuspended()) {
+                    ps.removeSuspension(suspendingPackagePredicate, userId);
+                    if (!ps.getUserStateOrDefault(userId).isSuspended()) {
+                        unsuspendedPackages.add(ps.getPackageName());
+                        unsuspendedUids.add(UserHandle.getUid(userId, ps.getAppId()));
+                    }
+                }
+            }
+            mPm.scheduleWritePackageRestrictionsLocked(userId);
+        }
+        if (!unsuspendedPackages.isEmpty()) {
+            final String[] packageArray = unsuspendedPackages.toArray(
+                    new String[unsuspendedPackages.size()]);
+            sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
+            sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+                    packageArray, unsuspendedUids.toArray(), userId);
+        }
+    }
+
+    /**
+     * Returns the launcher extras for the given suspended package.
+     *
+     * @param packageName The name of the suspended package.
+     * @param userId The user where the package resides.
+     * @param callingUid The caller's uid.
+     * @return The launcher extras.
+     */
+    @Nullable
+    Bundle getSuspendedPackageLauncherExtras(@NonNull String packageName, int userId,
+            int callingUid) {
+        final PackageStateInternal packageState = mPm.getPackageStateInternal(
+                packageName, callingUid);
+        if (packageState == null) {
+            return null;
+        }
+        Bundle allExtras = new Bundle();
+        PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+        if (userState.isSuspended()) {
+            for (int i = 0; i < userState.getSuspendParams().size(); i++) {
+                final SuspendParams params = userState.getSuspendParams().valueAt(i);
+                if (params != null && params.getLauncherExtras() != null) {
+                    allExtras.putAll(params.getLauncherExtras());
+                }
+            }
+        }
+        return (allExtras.size() > 0) ? allExtras : null;
+    }
+
+    /**
+     * Return {@code true}, if the given package is suspended.
+     *
+     * @param packageName The name of package to check.
+     * @param userId The user where the package resides.
+     * @param callingUid The caller's uid.
+     * @return {@code true}, if the given package is suspended.
+     */
+    boolean isPackageSuspended(@NonNull String packageName, int userId, int callingUid) {
+        final PackageStateInternal packageState = mPm.getPackageStateInternal(
+                packageName, callingUid);
+        return packageState != null && packageState.getUserStateOrDefault(userId)
+                .isSuspended();
+    }
+
+    /**
+     * Given a suspended package, returns the name of package which invokes suspending to it.
+     *
+     * @param suspendedPackage The suspended package to check.
+     * @param userId The user where the package resides.
+     * @param callingUid The caller's uid.
+     * @return The name of suspending package.
+     */
+    @Nullable
+    String getSuspendingPackage(@NonNull String suspendedPackage, int userId, int callingUid) {
+        final PackageStateInternal packageState = mPm.getPackageStateInternal(
+                suspendedPackage, callingUid);
+        if (packageState == null) {
+            return  null;
+        }
+
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+        if (!userState.isSuspended()) {
+            return null;
+        }
+
+        String suspendingPackage = null;
+        for (int i = 0; i < userState.getSuspendParams().size(); i++) {
+            suspendingPackage = userState.getSuspendParams().keyAt(i);
+            if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+                return suspendingPackage;
+            }
+        }
+        return suspendingPackage;
+    }
+
+    /**
+     *  Returns the dialog info of the given suspended package.
+     *
+     * @param suspendedPackage The name of the suspended package.
+     * @param suspendingPackage The name of the suspending package.
+     * @param userId The user where the package resides.
+     * @param callingUid The caller's uid.
+     * @return The dialog info.
+     */
+    @Nullable
+    SuspendDialogInfo getSuspendedDialogInfo(@NonNull String suspendedPackage,
+            @NonNull String suspendingPackage, int userId, int callingUid) {
+        final PackageStateInternal packageState = mPm.getPackageStateInternal(
+                suspendedPackage, callingUid);
+        if (packageState == null) {
+            return  null;
+        }
+
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+        if (!userState.isSuspended()) {
+            return null;
+        }
+
+        final ArrayMap<String, SuspendParams> suspendParamsMap = userState.getSuspendParams();
+        if (suspendParamsMap == null) {
+            return null;
+        }
+
+        final SuspendParams suspendParams = suspendParamsMap.get(suspendingPackage);
+        return (suspendParams != null) ? suspendParams.getDialogInfo() : null;
+    }
+
+    /**
+     * Return {@code true} if the user is allowed to suspend packages by the caller.
+     *
+     * @param userId The user id to check.
+     * @param callingUid The caller's uid.
+     * @return {@code true} if the user is allowed to suspend packages by the caller.
+     */
+    boolean isSuspendAllowedForUser(int userId, int callingUid) {
+        final UserManagerService userManager = mInjector.getUserManagerService();
+        return isCallerDeviceOrProfileOwner(userId, callingUid)
+                || (!userManager.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)
+                && !userManager.hasUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, userId));
+    }
+
+    /**
+     * Returns an array of booleans, such that the ith boolean denotes whether the ith package can
+     * be suspended or not.
+     *
+     * @param packageNames  The package names to check suspendability for.
+     * @param userId The user to check in
+     * @param callingUid The caller's uid.
+     * @return An array containing results of the checks
+     */
+    @NonNull
+    boolean[] canSuspendPackageForUser(@NonNull String[] packageNames, int userId, int callingUid) {
+        final boolean[] canSuspend = new boolean[packageNames.length];
+        final boolean isCallerOwner = isCallerDeviceOrProfileOwner(userId, callingUid);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
+            final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
+            final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
+            final String requiredInstallerPackage = getKnownPackageName(PACKAGE_INSTALLER, userId);
+            final String requiredUninstallerPackage =
+                    getKnownPackageName(PACKAGE_UNINSTALLER, userId);
+            final String requiredVerifierPackage = getKnownPackageName(PACKAGE_VERIFIER, userId);
+            final String requiredPermissionControllerPackage =
+                    getKnownPackageName(PACKAGE_PERMISSION_CONTROLLER, userId);
+            for (int i = 0; i < packageNames.length; i++) {
+                canSuspend[i] = false;
+                final String packageName = packageNames[i];
+
+                if (mPm.isPackageDeviceAdmin(packageName, userId)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": has an active device admin");
+                    continue;
+                }
+                if (packageName.equals(activeLauncherPackageName)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": contains the active launcher");
+                    continue;
+                }
+                if (packageName.equals(requiredInstallerPackage)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": required for package installation");
+                    continue;
+                }
+                if (packageName.equals(requiredUninstallerPackage)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": required for package uninstallation");
+                    continue;
+                }
+                if (packageName.equals(requiredVerifierPackage)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": required for package verification");
+                    continue;
+                }
+                if (packageName.equals(dialerPackageName)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": is the default dialer");
+                    continue;
+                }
+                if (packageName.equals(requiredPermissionControllerPackage)) {
+                    Slog.w(TAG, "Cannot suspend package \"" + packageName
+                            + "\": required for permissions management");
+                    continue;
+                }
+                synchronized (mPm.mLock) {
+                    if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+                        Slog.w(TAG, "Cannot suspend package \"" + packageName
+                                + "\": protected package");
+                        continue;
+                    }
+                    if (!isCallerOwner && mPm.mSettings.getBlockUninstallLPr(userId, packageName)) {
+                        Slog.w(TAG, "Cannot suspend package \"" + packageName
+                                + "\": blocked by admin");
+                        continue;
+                    }
+
+                    AndroidPackage pkg = mPm.mPackages.get(packageName);
+                    if (pkg != null) {
+                        // Cannot suspend SDK libs as they are controlled by SDK manager.
+                        if (pkg.isSdkLibrary()) {
+                            Slog.w(TAG, "Cannot suspend package: " + packageName
+                                    + " providing SDK library: "
+                                    + pkg.getSdkLibName());
+                            continue;
+                        }
+                        // Cannot suspend static shared libs as they are considered
+                        // a part of the using app (emulating static linking). Also
+                        // static libs are installed always on internal storage.
+                        if (pkg.isStaticSharedLibrary()) {
+                            Slog.w(TAG, "Cannot suspend package: " + packageName
+                                    + " providing static shared library: "
+                                    + pkg.getStaticSharedLibName());
+                            continue;
+                        }
+                    }
+                }
+                if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
+                    Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
+                    continue;
+                }
+                canSuspend[i] = true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return canSuspend;
+    }
+
+    /**
+     * Send broadcast intents for packages suspension changes.
+     *
+     * @param intent The action name of the suspension intent.
+     * @param pkgList The names of packages which have suspension changes.
+     * @param uidList The uids of packages which have suspension changes.
+     * @param userId The user where packages reside.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
+            @NonNull int[] uidList, int userId) {
+        final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
+        final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
+        final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
+        final int[] userIds = new int[] {userId};
+        // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
+        // allow lists are the same.
+        for (int i = 0; i < pkgList.length; i++) {
+            final String pkgName = pkgList[i];
+            final int uid = uidList[i];
+            SparseArray<int[]> allowList = mInjector.getAppsFilter().getVisibilityAllowList(
+                    mPm.getPackageStateInternal(pkgName, SYSTEM_UID),
+                    userIds, mPm.getPackageStates());
+            if (allowList == null) {
+                allowList = new SparseArray<>(0);
+            }
+            boolean merged = false;
+            for (int j = 0; j < allowListsToSend.size(); j++) {
+                if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
+                    pkgsToSend.get(j).add(pkgName);
+                    uidsToSend.get(j).add(uid);
+                    merged = true;
+                    break;
+                }
+            }
+            if (!merged) {
+                pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
+                uidsToSend.add(IntArray.wrap(new int[] {uid}));
+                allowListsToSend.add(allowList);
+            }
+        }
+
+        final Handler handler = mInjector.getHandler();
+        for (int i = 0; i < pkgsToSend.size(); i++) {
+            final Bundle extras = new Bundle(3);
+            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                    pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
+            extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
+            final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
+                    ? null : allowListsToSend.get(i);
+            handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
+                    extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+                    null /* finishedReceiver */, userIds, null /* instantUserIds */,
+                    allowList, null /* bOptions */));
+        }
+    }
+
+    private String getKnownPackageName(@KnownPackage int knownPackage, int userId) {
+        final String[] knownPackages = mPm.getKnownPackageNamesInternal(knownPackage, userId);
+        return knownPackages.length > 0 ? knownPackages[0] : null;
+    }
+
+    private boolean isCallerDeviceOrProfileOwner(int userId, int callingUid) {
+        if (callingUid == SYSTEM_UID) {
+            return true;
+        }
+        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+        if (ownerPackage != null) {
+            return callingUid == mPm.getPackageUidInternal(
+                    ownerPackage, 0, userId, callingUid);
+        }
+        return false;
+    }
+
+    private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended,
+            int userId) {
+        final Handler handler = mInjector.getHandler();
+        final String action = suspended
+                ? Intent.ACTION_MY_PACKAGE_SUSPENDED
+                : Intent.ACTION_MY_PACKAGE_UNSUSPENDED;
+        handler.post(() -> {
+            final IActivityManager am = ActivityManager.getService();
+            if (am == null) {
+                Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ "
+                        + (suspended ? "" : "UN") + "SUSPENDED broadcasts");
+                return;
+            }
+            final int[] targetUserIds = new int[] {userId};
+            for (String packageName : affectedPackages) {
+                final Bundle appExtras = suspended
+                        ? getSuspendedPackageAppExtras(packageName, userId, SYSTEM_UID)
+                        : null;
+                final Bundle intentExtras;
+                if (appExtras != null) {
+                    intentExtras = new Bundle(1);
+                    intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras);
+                } else {
+                    intentExtras = null;
+                }
+                handler.post(() -> mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
+                        Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
+                        targetUserIds, false, null, null));
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d29dbbc..d6e88f4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3675,9 +3675,18 @@
             @UserInfoFlag int flags, @UserIdInt int parentId,
             @Nullable String[] disallowedPackages)
             throws UserManager.CheckedUserOperationException {
-        String restriction = (UserManager.isUserTypeManagedProfile(userType))
-                ? UserManager.DISALLOW_ADD_MANAGED_PROFILE
-                : UserManager.DISALLOW_ADD_USER;
+
+        // Checking user restriction before creating new user,
+        // default check is for DISALLOW_ADD_USER
+        // If new user is of type CLONE, check if creation of clone profile is allowed
+        // If new user is of type MANAGED, check if creation of managed profile is allowed
+        String restriction = UserManager.DISALLOW_ADD_USER;
+        if (UserManager.isUserTypeCloneProfile(userType)) {
+            restriction = UserManager.DISALLOW_ADD_CLONE_PROFILE;
+        } else if (UserManager.isUserTypeManagedProfile(userType)) {
+            restriction = UserManager.DISALLOW_ADD_MANAGED_PROFILE;
+        }
+
         enforceUserRestriction(restriction, UserHandle.getCallingUserId(),
                 "Cannot add user");
         return createUserInternalUnchecked(name, userType, flags, parentId,
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 0f3b4bc..1fa9013 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -101,6 +101,7 @@
             UserManager.DISALLOW_FACTORY_RESET,
             UserManager.DISALLOW_ADD_USER,
             UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+            UserManager.DISALLOW_ADD_CLONE_PROFILE,
             UserManager.ENSURE_VERIFY_APPS,
             UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
             UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
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/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 4bbe373..d455be7 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -162,7 +162,10 @@
      * The delay to wait before revoking on the event an app is terminated. Recommended to be long
      * enough so that apps don't lose permission on an immediate restart
      */
-    private static long getKilledDelayMillis() {
+    private long getKilledDelayMillis(boolean isSelfRevokedPermissionSession) {
+        if (isSelfRevokedPermissionSession) {
+            return 0;
+        }
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
                 PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS);
     }
@@ -175,6 +178,18 @@
         mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED));
     }
 
+    void setSelfRevokedPermissionSession(int uid) {
+        synchronized (mLock) {
+            PackageInactivityListener listener = mListeners.get(uid);
+            if (listener == null) {
+                Log.e(LOG_TAG, "Could not set session for uid " + uid
+                        + " as self-revoke session: session not found");
+                return;
+            }
+            listener.setSelfRevokedPermissionSession();
+        }
+    }
+
     /**
      * A class which watches a package for inactivity and notifies the permission controller when
      * the package becomes inactive
@@ -189,6 +204,7 @@
         private final int mImportanceToResetTimer;
         private final int mImportanceToKeepSessionAlive;
 
+        private boolean mIsSelfRevokedPermissionSession;
         private boolean mIsAlarmSet;
         private boolean mIsFinished;
 
@@ -255,7 +271,7 @@
                             }
                             onImportanceChanged(mUid, imp);
                         }
-                    }, mToken, getKilledDelayMillis());
+                    }, mToken, getKilledDelayMillis(mIsSelfRevokedPermissionSession));
                     return;
                 }
                 if (importance > mImportanceToResetTimer) {
@@ -291,6 +307,14 @@
         }
 
         /**
+         * Marks the session as a self-revoke session, which does not delay the revocation when
+         * the app is restarting.
+         */
+        public void setSelfRevokedPermissionSession() {
+            mIsSelfRevokedPermissionSession = true;
+        }
+
+        /**
          * Set the alarm which will callback when the package is inactive
          */
         @GuardedBy("mInnerLock")
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 1cfcdf51..317730a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -66,6 +66,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
@@ -558,9 +559,18 @@
     }
 
     @Override
-    public void selfRevokePermissions(@NonNull String packageName,
+    public void revokeOwnPermissionsOnKill(@NonNull String packageName,
             @NonNull List<String> permissions) {
-        mPermissionManagerServiceImpl.selfRevokePermissions(packageName, permissions);
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        AndroidFuture<Void> future = new AndroidFuture<>();
+        future.whenComplete((result, err) -> {
+            if (err == null) {
+                getOneTimePermissionUserManager(callingUserId)
+                        .setSelfRevokedPermissionSession(callingUid);
+            }
+        });
+        mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions, future);
     }
 
     @Override
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 981fd8e..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;
@@ -113,6 +108,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.os.RoSystemProperties;
@@ -135,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;
 
@@ -1592,7 +1592,8 @@
     }
 
     @Override
-    public void selfRevokePermissions(String packageName, List<String> permissions) {
+    public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions,
+            AndroidFuture<Void> callback) {
         final int callingUid = Binder.getCallingUid();
         int callingUserId = UserHandle.getUserId(callingUid);
         int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId);
@@ -1607,7 +1608,8 @@
                         + permName + " because it does not hold that permission");
             }
         }
-        mPermissionControllerManager.selfRevokePermissions(packageName, permissions);
+        mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions,
+                callback);
     }
 
     private boolean mayManageRolePermission(int uid) {
@@ -3169,22 +3171,29 @@
                 }
             } 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);
-                    // TODO(b/205888750): remove revoke once propagated through droidfood
-                    if (ps.isPermissionGranted(newPerm)) {
+                    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) {
                         ps.revokePermission(bp);
+                    } else if (!ps.isPermissionGranted(newPerm)
+                            && 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);
                 }
             }
         }
@@ -4772,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/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index c582f9e..91c558b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -27,6 +27,7 @@
 import android.permission.IOnPermissionsChangeListener;
 import android.permission.PermissionManagerInternal;
 
+import com.android.internal.infra.AndroidFuture;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import java.io.FileDescriptor;
@@ -343,8 +344,10 @@
      *
      * @param packageName The name of the package for which the permissions will be revoked.
      * @param permissions List of permissions to be revoked.
+     * @param callback Callback called when the revocation request has been completed.
      */
-    void selfRevokePermissions(String packageName, List<String> permissions);
+    void revokeOwnPermissionsOnKill(String packageName, List<String> permissions,
+            AndroidFuture<Void> callback);
 
     /**
      * Get whether you should show UI with rationale for requesting a permission. You should do this
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/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 18a6435..52d9b7a 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -26,6 +26,10 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager.Property;
 import android.content.pm.SigningDetails;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
 import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedApexSystemService;
 import com.android.server.pm.pkg.component.ParsedAttribution;
@@ -37,9 +41,6 @@
 import com.android.server.pm.pkg.component.ParsedProvider;
 import com.android.server.pm.pkg.component.ParsedService;
 import com.android.server.pm.pkg.component.ParsedUsesPermission;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
 
 import java.security.PublicKey;
 import java.util.Map;
@@ -286,6 +287,9 @@
 
     ParsingPackage setInstallLocation(int installLocation);
 
+    /** @see R#styleable.AndroidManifest_inheritKeyStoreKeys */
+    ParsingPackage setInheritKeyStoreKeys(boolean inheritKeyStoreKeys);
+
     ParsingPackage setLabelRes(int labelRes);
 
     ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
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 c4de862..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);
@@ -492,6 +491,10 @@
                 ENABLED,
                 DISALLOW_PROFILING,
                 REQUEST_FOREGROUND_SERVICE_EXEMPTION,
+                ATTRIBUTIONS_ARE_USER_VISIBLE,
+                RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED,
+                SDK_LIBRARY,
+                INHERIT_KEYSTORE_KEYS,
         })
         public @interface Values {}
         private static final long EXTERNAL_STORAGE = 1L;
@@ -544,6 +547,7 @@
         private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47;
         private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48;
         private static final long SDK_LIBRARY = 1L << 49;
+        private static final long INHERIT_KEYSTORE_KEYS = 1L << 50;
     }
 
     private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
@@ -655,6 +659,7 @@
         return this;
     }
 
+    @SuppressLint("MissingSuperCall")
     @CallSuper
     @Override
     public Object hideAsParsed() {
@@ -1120,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;
@@ -1139,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);
@@ -1252,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);
@@ -1901,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
@@ -1925,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
@@ -2371,6 +2377,11 @@
     }
 
     @Override
+    public boolean shouldInheritKeyStoreKeys() {
+        return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS);
+    }
+
+    @Override
     public ParsingPackageImpl setBaseRevisionCode(int value) {
         baseRevisionCode = value;
         return this;
@@ -2514,6 +2525,11 @@
     }
 
     @Override
+    public ParsingPackageImpl setInheritKeyStoreKeys(boolean value) {
+        return setBoolean(Booleans.INHERIT_KEYSTORE_KEYS, value);
+    }
+
+    @Override
     public ParsingPackageImpl setLabelRes(int value) {
         labelRes = value;
         return this;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
index 1497112..4b659a14 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java
@@ -350,4 +350,9 @@
      * @see R.styleable#AndroidManifestApplication_localeConfig
      */
     int getLocaleConfigRes();
+
+    /**
+     * @see R.styleable#AndroidManifest_inheritKeyStoreKeys
+     */
+    boolean shouldInheritKeyStoreKeys();
 }
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 1ce01f6..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);
         }
@@ -868,7 +891,9 @@
                 .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
-                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
+                .setInheritKeyStoreKeys(bool(false,
+                        R.styleable.AndroidManifest_inheritKeyStoreKeys, sa));
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
@@ -1671,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()) {
@@ -2677,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);
@@ -2714,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);
             }
         }
@@ -2989,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 16176f0..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;
@@ -121,7 +117,6 @@
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
@@ -198,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;
@@ -231,7 +227,6 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -246,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.
@@ -342,6 +337,7 @@
     private WifiManager mWifiManager;
     private TelephonyManager mTelephony;
     private SubscriptionManager mSubscriptionManager;
+    private NetworkStatsManager mNetworkStatsManager;
 
     @GuardedBy("mKernelWakelockLock")
     private KernelWakelockReader mKernelWakelockReader;
@@ -790,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();
 
@@ -983,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) {
@@ -1139,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;
             }
@@ -1156,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;
@@ -1247,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);
         }
@@ -1271,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()));
         }
     }
 
@@ -1292,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.
@@ -1311,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()));
             }
         }
     }
@@ -1387,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(
@@ -1426,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);
                 });
     }
 
@@ -1456,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;
     }
@@ -2332,11 +2313,7 @@
         List<ProcessMemoryState> managedProcessList =
                 LocalServices.getService(ActivityManagerInternal.class)
                         .getMemoryStateForProcesses();
-        managedProcessList.sort(Comparator.comparingInt(x -> x.oomScore));
         for (ProcessMemoryState process : managedProcessList) {
-            if (process.uid == Process.SYSTEM_UID) {
-                continue;
-            }
             KernelAllocationStats.ProcessDmabuf proc =
                     KernelAllocationStats.getDmabufAllocations(process.pid);
             if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) {
@@ -2353,6 +2330,32 @@
                             proc.mappedSizeKb,
                             proc.mappedBuffersCount));
         }
+        SparseArray<String> processCmdlines = getProcessCmdlines();
+        managedProcessList.forEach(managedProcess -> processCmdlines.delete(managedProcess.pid));
+        int size = processCmdlines.size();
+        for (int i = 0; i < size; ++i) {
+            int pid = processCmdlines.keyAt(i);
+            int uid = getUidForPid(pid);
+            // ignore root processes (unlikely to be interesting)
+            if (uid <= 0) {
+                continue;
+            }
+            KernelAllocationStats.ProcessDmabuf proc =
+                    KernelAllocationStats.getDmabufAllocations(pid);
+            if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) {
+                continue;
+            }
+            pulledData.add(
+                    FrameworkStatsLog.buildStatsEvent(
+                            atomTag,
+                            uid,
+                            processCmdlines.valueAt(i),
+                            -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/,
+                            proc.retainedSizeKb,
+                            proc.retainedBuffersCount,
+                            proc.mappedSizeKb,
+                            proc.mappedBuffersCount));
+        }
         return StatsManager.PULL_SUCCESS;
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 17f5566..0edd06a 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -22,6 +22,7 @@
 import static android.app.StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
 import static android.app.StatusBarManager.NavBarModeOverride;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -38,6 +39,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.om.IOverlayManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
@@ -60,6 +62,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -79,6 +82,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.statusbar.IAddTileResultCallback;
@@ -154,6 +158,8 @@
     private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>();
     private static final long REQUEST_TIME_OUT = TimeUnit.MINUTES.toNanos(5);
 
+    private IOverlayManager mOverlayManager;
+
     private class DeathRecipient implements IBinder.DeathRecipient {
         public void binderDied() {
             mBar.asBinder().unlinkToDeath(this,0);
@@ -256,6 +262,18 @@
         mTileRequestTracker = new TileRequestTracker(mContext);
     }
 
+    private IOverlayManager getOverlayManager() {
+        // No need to synchronize; worst-case scenario it will be fetched twice.
+        if (mOverlayManager == null) {
+            mOverlayManager = IOverlayManager.Stub.asInterface(
+                    ServiceManager.getService(Context.OVERLAY_SERVICE));
+            if (mOverlayManager == null) {
+                Slog.w("StatusBarManager", "warning: no OVERLAY_SERVICE");
+            }
+        }
+        return mOverlayManager;
+    }
+
     @Override
     public void onDisplayAdded(int displayId) {}
 
@@ -1296,6 +1314,11 @@
         });
     }
 
+    @VisibleForTesting
+    void registerOverlayManager(IOverlayManager overlayManager) {
+        mOverlayManager = overlayManager;
+    }
+
     /**
      * @param clearNotificationEffects whether to consider notifications as "shown" and stop
      *     LED, vibration, and ringing
@@ -1869,6 +1892,14 @@
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                     Settings.Secure.NAV_BAR_KIDS_MODE, navBarModeOverride, userId);
+
+            IOverlayManager overlayManager = getOverlayManager();
+            if (overlayManager != null && navBarModeOverride == NAV_BAR_MODE_OVERRIDE_KIDS
+                    && isPackageSupported(NAV_BAR_MODE_3BUTTON_OVERLAY)) {
+                overlayManager.setEnabledExclusiveInCategory(NAV_BAR_MODE_3BUTTON_OVERLAY, userId);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         } finally {
             Binder.restoreCallingIdentity(userIdentity);
         }
@@ -1896,6 +1927,21 @@
         return navBarKidsMode;
     }
 
+    private boolean isPackageSupported(String packageName) {
+        if (packageName == null) {
+            return false;
+        }
+        try {
+            return mContext.getPackageManager().getPackageInfo(packageName,
+                    PackageManager.PackageInfoFlags.of(0)) != null;
+        } catch (PackageManager.NameNotFoundException ignored) {
+            if (SPEW) {
+                Slog.d(TAG, "Package not found: " + packageName);
+            }
+        }
+        return false;
+    }
+
     /** @hide */
     public void passThroughShellCommand(String[] args, FileDescriptor fd) {
         enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 3093509..c0207f0 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -37,6 +37,7 @@
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.provider.DeviceConfig;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.DataUnit;
@@ -255,11 +256,14 @@
     private void checkHigh() {
         final StorageManager storage = getContext().getSystemService(StorageManager.class);
         // Check every mounted private volume to see if they're under the high storage threshold
-        // which is StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space
+        // which is storageThresholdPercentHigh of total space
+        final int storageThresholdPercentHigh = DeviceConfig.getInt(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY,
+                StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH);
         for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
             final File file = vol.getPath();
-            if (file.getUsableSpace() < file.getTotalSpace()
-                    * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH / 100) {
+            if (file.getUsableSpace() < file.getTotalSpace() * storageThresholdPercentHigh / 100) {
                 final PackageManagerService pms = (PackageManagerService) ServiceManager
                         .getService("package");
                 try {
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 79231f7..06ce4a4 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -16,6 +16,8 @@
 
 package com.android.server.trust;
 
+import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE;
+
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -99,6 +101,7 @@
     // Trust state
     private boolean mTrusted;
     private CharSequence mMessage;
+    private boolean mDisplayTrustGrantedMessage;
     private boolean mTrustDisabledByDpm;
     private boolean mManagingTrust;
     private IBinder mSetTrustAgentFeaturesToken;
@@ -132,6 +135,7 @@
                     mTrusted = true;
                     mMessage = (CharSequence) msg.obj;
                     int flags = msg.arg1;
+                    mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
                     long durationMs = msg.getData().getLong(DATA_DURATION);
                     if (durationMs > 0) {
                         final long duration;
@@ -166,6 +170,7 @@
                     // Fall through.
                 case MSG_REVOKE_TRUST:
                     mTrusted = false;
+                    mDisplayTrustGrantedMessage = false;
                     mMessage = null;
                     mHandler.removeMessages(MSG_TRUST_TIMEOUT);
                     if (msg.what == MSG_REVOKE_TRUST) {
@@ -199,6 +204,7 @@
                     mManagingTrust = msg.arg1 != 0;
                     if (!mManagingTrust) {
                         mTrusted = false;
+                        mDisplayTrustGrantedMessage = false;
                         mMessage = null;
                     }
                     mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust);
@@ -271,12 +277,13 @@
     private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() {
 
         @Override
-        public void grantTrust(CharSequence userMessage, long durationMs, int flags) {
-            if (DEBUG) Slog.d(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs
+        public void grantTrust(CharSequence message, long durationMs, int flags) {
+            if (DEBUG) {
+                Slog.d(TAG, "enableTrust(" + message + ", durationMs = " + durationMs
                         + ", flags = " + flags + ")");
+            }
 
-            Message msg = mHandler.obtainMessage(
-                    MSG_GRANT_TRUST, flags, 0, userMessage);
+            Message msg = mHandler.obtainMessage(MSG_GRANT_TRUST, flags, 0, message);
             msg.getData().putLong(DATA_DURATION, durationMs);
             msg.sendToTarget();
         }
@@ -461,6 +468,19 @@
     }
 
     /**
+     * @see android.service.trust.TrustAgentService#onUserRequestedUnlock()
+     */
+    public void onUserRequestedUnlock() {
+        try {
+            if (mTrustAgentService != null) {
+                mTrustAgentService.onUserRequestedUnlock();
+            }
+        } catch (RemoteException e) {
+            onError(e);
+        }
+    }
+
+    /**
      * @see android.service.trust.TrustAgentService#onUnlockLockout(int)
      */
     public void onUnlockLockout(int timeoutMs) {
@@ -579,6 +599,14 @@
         return mMessage;
     }
 
+    /**
+     * Whether the trust agent would like to display {@link #getMessage()} to the user when trust
+     * is granted.
+     */
+    public boolean shouldDisplayTrustGrantedMessage() {
+        return mDisplayTrustGrantedMessage;
+    }
+
     public void destroy() {
         mHandler.removeMessages(MSG_RESTART_TIMEOUT);
 
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index fc87253..9bed24d 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -75,7 +75,6 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -123,6 +122,7 @@
     private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
     private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14;
     private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15;
+    public static final int MSG_USER_REQUESTED_UNLOCK = 16;
 
     private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except";
 
@@ -391,7 +391,6 @@
         }
     }
 
-
     public void updateTrust(int userId, int flags) {
         updateTrust(userId, flags, false /* isFromUnlock */);
     }
@@ -431,7 +430,7 @@
             changed = mUserIsTrusted.get(userId) != trusted;
             mUserIsTrusted.put(userId, trusted);
         }
-        dispatchOnTrustChanged(trusted, userId, flags);
+        dispatchOnTrustChanged(trusted, userId, flags, getTrustGrantedMessages(userId));
         if (changed) {
             refreshDeviceLockedForUser(userId);
             if (!trusted) {
@@ -951,6 +950,24 @@
         return false;
     }
 
+    private List<String> getTrustGrantedMessages(int userId) {
+        if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+            return new ArrayList<>();
+        }
+
+        List<String> trustGrantedMessages = new ArrayList<>();
+        for (int i = 0; i < mActiveAgents.size(); i++) {
+            AgentInfo info = mActiveAgents.valueAt(i);
+            if (info.userId == userId
+                    && info.agent.isTrusted()
+                    && info.agent.shouldDisplayTrustGrantedMessage()
+                    && !TextUtils.isEmpty(info.agent.getMessage())) {
+                trustGrantedMessages.add(info.agent.getMessage().toString());
+            }
+        }
+        return trustGrantedMessages;
+    }
+
     private boolean aggregateIsTrustManaged(int userId) {
         if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
             return false;
@@ -981,6 +998,15 @@
         }
     }
 
+    private void dispatchUserRequestedUnlock(int userId) {
+        for (int i = 0; i < mActiveAgents.size(); i++) {
+            AgentInfo info = mActiveAgents.valueAt(i);
+            if (info.userId == userId) {
+                info.agent.onUserRequestedUnlock();
+            }
+        }
+    }
+
     private void dispatchUnlockLockout(int timeoutMs, int userId) {
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
@@ -1011,7 +1037,8 @@
         }
     }
 
-    private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) {
+    private void dispatchOnTrustChanged(boolean enabled, int userId, int flags,
+            @NonNull List<String> trustGrantedMessages) {
         if (DEBUG) {
             Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x"
                     + Integer.toHexString(flags) + ")");
@@ -1019,7 +1046,7 @@
         if (!enabled) flags = 0;
         for (int i = 0; i < mTrustListeners.size(); i++) {
             try {
-                mTrustListeners.get(i).onTrustChanged(enabled, userId, flags);
+                mTrustListeners.get(i).onTrustChanged(enabled, userId, flags, trustGrantedMessages);
             } catch (DeadObjectException e) {
                 Slog.d(TAG, "Removing dead TrustListener.");
                 mTrustListeners.remove(i);
@@ -1110,6 +1137,12 @@
         }
 
         @Override
+        public void reportUserRequestedUnlock(int userId) throws RemoteException {
+            enforceReportPermission();
+            mHandler.obtainMessage(MSG_USER_REQUESTED_UNLOCK, userId).sendToTarget();
+        }
+
+        @Override
         public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException {
             enforceReportPermission();
             mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId)
@@ -1389,6 +1422,9 @@
                 case MSG_DISPATCH_UNLOCK_ATTEMPT:
                     dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2);
                     break;
+                case MSG_USER_REQUESTED_UNLOCK:
+                    dispatchUserRequestedUnlock(msg.arg1);
+                    break;
                 case MSG_DISPATCH_UNLOCK_LOCKOUT:
                     dispatchUnlockLockout(msg.arg1, msg.arg2);
                     break;
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
similarity index 97%
rename from services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
rename to services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 6058d88..b3649a7 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -34,15 +34,16 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.ITvIAppManager;
+import android.media.tv.interactive.AppLinkInfo;
 import android.media.tv.interactive.ITvInteractiveAppClient;
+import android.media.tv.interactive.ITvInteractiveAppManager;
 import android.media.tv.interactive.ITvInteractiveAppManagerCallback;
 import android.media.tv.interactive.ITvInteractiveAppService;
 import android.media.tv.interactive.ITvInteractiveAppServiceCallback;
 import android.media.tv.interactive.ITvInteractiveAppSession;
 import android.media.tv.interactive.ITvInteractiveAppSessionCallback;
-import android.media.tv.interactive.TvIAppService;
 import android.media.tv.interactive.TvInteractiveAppInfo;
+import android.media.tv.interactive.TvInteractiveAppService;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -79,9 +80,9 @@
 /**
  * This class provides a system service that manages interactive TV applications.
  */
-public class TvIAppManagerService extends SystemService {
+public class TvInteractiveAppManagerService extends SystemService {
     private static final boolean DEBUG = false;
-    private static final String TAG = "TvIAppManagerService";
+    private static final String TAG = "TvInteractiveAppManagerService";
     // A global lock.
     private final Object mLock = new Object();
     private final Context mContext;
@@ -95,6 +96,10 @@
     @GuardedBy("mLock")
     private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
+    // TODO: remove mGetServiceListCalled if onBootPhrase work correctly
+    @GuardedBy("mLock")
+    private boolean mGetServiceListCalled = false;
+
     private final UserManager mUserManager;
 
     /**
@@ -106,7 +111,7 @@
      *
      * @param context The system server context.
      */
-    public TvIAppManagerService(Context context) {
+    public TvInteractiveAppManagerService(Context context) {
         super(context);
         mContext = context;
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
@@ -122,7 +127,7 @@
         }
         PackageManager pm = mContext.getPackageManager();
         List<ResolveInfo> services = pm.queryIntentServicesAsUser(
-                new Intent(TvIAppService.SERVICE_INTERFACE),
+                new Intent(TvInteractiveAppService.SERVICE_INTERFACE),
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                 userId);
         List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
@@ -256,15 +261,16 @@
 
     @GuardedBy("mLock")
     private void notifyStateChangedLocked(
-            UserState userState, String iAppServiceId, int type, int state) {
+            UserState userState, String iAppServiceId, int type, int state, int err) {
         if (DEBUG) {
             Slog.d(TAG, "notifyRteStateChanged(iAppServiceId="
-                    + iAppServiceId + ", type=" + type + ", state=" + state + ")");
+                    + iAppServiceId + ", type=" + type + ", state=" + state + ", err=" + err + ")");
         }
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
             try {
-                userState.mCallbacks.getBroadcastItem(i).onStateChanged(iAppServiceId, type, state);
+                userState.mCallbacks.getBroadcastItem(i)
+                        .onStateChanged(iAppServiceId, type, state, err);
             } catch (RemoteException e) {
                 Slog.e(TAG, "failed to report RTE state changed", e);
             }
@@ -287,7 +293,7 @@
         if (DEBUG) {
             Slogf.d(TAG, "onStart");
         }
-        publishBinderService(Context.TV_IAPP_SERVICE, new BinderService());
+        publishBinderService(Context.TV_INTERACTIVE_APP_SERVICE, new BinderService());
     }
 
     @Override
@@ -628,7 +634,7 @@
         return session;
     }
 
-    private final class BinderService extends ITvIAppManager.Stub {
+    private final class BinderService extends ITvInteractiveAppManager.Stub {
 
         @Override
         public List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId) {
@@ -637,6 +643,10 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
+                    if (!mGetServiceListCalled) {
+                        buildTvInteractiveAppServiceListLocked(userId, null);
+                        mGetServiceListCalled = true;
+                    }
                     UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                     List<TvInteractiveAppInfo> iAppList = new ArrayList<>();
                     for (TvInteractiveAppState state : userState.mIAppMap.values()) {
@@ -686,9 +696,9 @@
         }
 
         @Override
-        public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+        public void registerAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "registerAppLinkInfo");
+                    Binder.getCallingUid(), userId, "registerAppLinkInfo: " + appLinkInfo);
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -722,9 +732,9 @@
         }
 
         @Override
-        public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+        public void unregisterAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userId, "unregisterAppLinkInfo");
+                    Binder.getCallingUid(), userId, "unregisterAppLinkInfo: " + appLinkInfo);
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -1361,7 +1371,7 @@
                 }
             } finally {
                 if (surface != null) {
-                    // surface is not used in TvIAppManagerService.
+                    // surface is not used in TvInteractiveAppManagerService.
                     surface.release();
                 }
                 Binder.restoreCallingIdentity(identity);
@@ -1678,7 +1688,7 @@
             }
 
             Intent i =
-                    new Intent(TvIAppService.SERVICE_INTERFACE).setComponent(component);
+                    new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component);
             serviceState.mBound = mContext.bindServiceAsUser(
                     i, serviceState.mConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
@@ -1810,7 +1820,7 @@
         private final ServiceConnection mConnection;
         private final ComponentName mComponent;
         private final String mIAppServiceId;
-        private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>();
+        private final List<Pair<AppLinkInfo, Boolean>> mPendingAppLinkInfo = new ArrayList<>();
         private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
 
         private boolean mPendingPrepare = false;
@@ -1833,7 +1843,7 @@
             mIAppServiceId = tias;
         }
 
-        private void addPendingAppLink(Bundle info, boolean register) {
+        private void addPendingAppLink(AppLinkInfo info, boolean register) {
             mPendingAppLinkInfo.add(Pair.create(info, register));
         }
 
@@ -1866,6 +1876,16 @@
                 ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
                 serviceState.mService = ITvInteractiveAppService.Stub.asInterface(service);
 
+                // Register a callback, if we need to.
+                if (serviceState.mCallback == null) {
+                    serviceState.mCallback = new ServiceCallback(mComponent, mUserId);
+                    try {
+                        serviceState.mService.registerCallback(serviceState.mCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in registerCallback", e);
+                    }
+                }
+
                 if (serviceState.mPendingPrepare) {
                     final long identity = Binder.clearCallingIdentity();
                     try {
@@ -1880,10 +1900,10 @@
                 }
 
                 if (!serviceState.mPendingAppLinkInfo.isEmpty()) {
-                    for (Iterator<Pair<Bundle, Boolean>> it =
+                    for (Iterator<Pair<AppLinkInfo, Boolean>> it =
                             serviceState.mPendingAppLinkInfo.iterator();
                             it.hasNext(); ) {
-                        Pair<Bundle, Boolean> appLinkInfoPair = it.next();
+                        Pair<AppLinkInfo, Boolean> appLinkInfoPair = it.next();
                         final long identity = Binder.clearCallingIdentity();
                         try {
                             if (appLinkInfoPair.second) {
@@ -1968,14 +1988,14 @@
         }
 
         @Override
-        public void onStateChanged(int type, int state) {
+        public void onStateChanged(int type, int state, int error) {
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
                     String iAppServiceId = serviceState.mIAppServiceId;
                     UserState userState = getUserStateLocked(mUserId);
-                    notifyStateChangedLocked(userState, iAppServiceId, type, state);
+                    notifyStateChangedLocked(userState, iAppServiceId, type, state, error);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -2072,7 +2092,7 @@
 
         @Override
         public void onCommandRequest(
-                @TvIAppService.InteractiveAppServiceCommandType String cmdType,
+                @TvInteractiveAppService.InteractiveAppServiceCommandType String cmdType,
                 Bundle parameters) {
             synchronized (mLock) {
                 if (DEBUG) {
@@ -2210,16 +2230,16 @@
         }
 
         @Override
-        public void onSessionStateChanged(int state) {
+        public void onSessionStateChanged(int state, int err) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slogf.d(TAG, "onSessionStateChanged (state=" + state + ")");
+                    Slogf.d(TAG, "onSessionStateChanged (state=" + state + ", err=" + err + ")");
                 }
                 if (mSessionState.mSession == null || mSessionState.mClient == null) {
                     return;
                 }
                 try {
-                    mSessionState.mClient.onSessionStateChanged(state, mSessionState.mSeq);
+                    mSessionState.mClient.onSessionStateChanged(state, err, mSessionState.mSeq);
                 } catch (RemoteException e) {
                     Slogf.e(TAG, "error in onSessionStateChanged", e);
                 }
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 3442704..6aa06e8 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -104,7 +104,8 @@
 import java.util.Objects;
 
 /** Manages uri grants. */
-public class UriGrantsManagerService extends IUriGrantsManager.Stub {
+public class UriGrantsManagerService extends IUriGrantsManager.Stub implements
+        UriMetricsHelper.PersistentUriGrantsProvider {
     private static final boolean DEBUG = false;
     private static final String TAG = "UriGrantsManagerService";
     // Maximum number of persisted Uri grants a package is allowed
@@ -115,6 +116,7 @@
     private final H mH;
     ActivityManagerInternal mAmInternal;
     PackageManagerInternal mPmInternal;
+    UriMetricsHelper mMetricsHelper;
 
     /** File storing persisted {@link #mGrantedUriPermissions}. */
     private final AtomicFile mGrantFile;
@@ -168,16 +170,19 @@
     }
 
     public static final class Lifecycle extends SystemService {
+        private final Context mContext;
         private final UriGrantsManagerService mService;
 
         public Lifecycle(Context context) {
             super(context);
+            mContext = context;
             mService = new UriGrantsManagerService();
         }
 
         @Override
         public void onStart() {
             publishBinderService(Context.URI_GRANTS_SERVICE, mService);
+            mService.mMetricsHelper = new UriMetricsHelper(mContext, mService);
             mService.start();
         }
 
@@ -186,6 +191,7 @@
             if (phase == PHASE_SYSTEM_SERVICES_READY) {
                 mService.mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
                 mService.mPmInternal = LocalServices.getService(PackageManagerInternal.class);
+                mService.mMetricsHelper.registerPuller();
             }
         }
 
@@ -1298,20 +1304,50 @@
         return false;
     }
 
+    @Override
+    public ArrayList<UriPermission> providePersistentUriGrants() {
+        final ArrayList<UriPermission> result = new ArrayList<>();
+
+        synchronized (mLock) {
+            final int size = mGrantedUriPermissions.size();
+            for (int i = 0; i < size; i++) {
+                final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+
+                final int permissionsForPackageSize = perms.size();
+                for (int j = 0; j < permissionsForPackageSize; j++) {
+                    final UriPermission permission = perms.valueAt(j);
+
+                    if (permission.persistedModeFlags != 0) {
+                        result.add(permission);
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
     private void writeGrantedUriPermissions() {
         if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()");
 
         final long startTime = SystemClock.uptimeMillis();
 
+        int persistentUriPermissionsCount = 0;
+
         // Snapshot permissions so we can persist without lock
         ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
         synchronized (mLock) {
             final int size = mGrantedUriPermissions.size();
             for (int i = 0; i < size; i++) {
                 final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
-                for (UriPermission perm : perms.values()) {
-                    if (perm.persistedModeFlags != 0) {
-                        persist.add(perm.snapshot());
+
+                final int permissionsForPackageSize = perms.size();
+                for (int j = 0; j < permissionsForPackageSize; j++) {
+                    final UriPermission permission = perms.valueAt(j);
+
+                    if (permission.persistedModeFlags != 0) {
+                        persistentUriPermissionsCount++;
+                        persist.add(permission.snapshot());
                     }
                 }
             }
@@ -1345,6 +1381,8 @@
                 mGrantFile.failWrite(fos);
             }
         }
+
+        mMetricsHelper.reportPersistentUriFlushed(persistentUriPermissionsCount);
     }
 
     final class H extends Handler {
diff --git a/services/core/java/com/android/server/uri/UriMetricsHelper.java b/services/core/java/com/android/server/uri/UriMetricsHelper.java
new file mode 100644
index 0000000..dbc9599
--- /dev/null
+++ b/services/core/java/com/android/server/uri/UriMetricsHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.uri;
+
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.util.SparseArray;
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+final class UriMetricsHelper {
+
+    private static final StatsManager.PullAtomMetadata DAILY_PULL_METADATA =
+            new StatsManager.PullAtomMetadata.Builder()
+                    .setCoolDownMillis(TimeUnit.DAYS.toMillis(1))
+                    .build();
+
+
+    private final Context mContext;
+    private final PersistentUriGrantsProvider mPersistentUriGrantsProvider;
+
+    UriMetricsHelper(Context context, PersistentUriGrantsProvider provider) {
+        mContext = context;
+        mPersistentUriGrantsProvider = provider;
+    }
+
+    void registerPuller() {
+        final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+        statsManager.setPullAtomCallback(
+                FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE,
+                DAILY_PULL_METADATA,
+                DIRECT_EXECUTOR,
+                (atomTag, data) -> {
+                    reportPersistentUriPermissionsPerPackage(data);
+                    return StatsManager.PULL_SUCCESS;
+                });
+    }
+
+    void reportPersistentUriFlushed(int amount) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_FLUSHED,
+                amount
+        );
+    }
+
+    private void reportPersistentUriPermissionsPerPackage(List<StatsEvent> data) {
+        final ArrayList<UriPermission> persistentUriGrants =
+                mPersistentUriGrantsProvider.providePersistentUriGrants();
+
+        final SparseArray<Integer> perUidCount = new SparseArray<>();
+
+        final int persistentUriGrantsSize = persistentUriGrants.size();
+        for (int i = 0; i < persistentUriGrantsSize; i++) {
+            final UriPermission uriPermission = persistentUriGrants.get(i);
+
+            perUidCount.put(
+                    uriPermission.targetUid,
+                    perUidCount.get(uriPermission.targetUid, 0) + 1
+            );
+        }
+
+        final int perUidCountSize = perUidCount.size();
+        for (int i = 0; i < perUidCountSize; i++) {
+            final int uid = perUidCount.keyAt(i);
+            final int amount = perUidCount.valueAt(i);
+
+            data.add(
+                    FrameworkStatsLog.buildStatsEvent(
+                            FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE,
+                            uid,
+                            amount
+                    )
+            );
+        }
+    }
+
+    interface PersistentUriGrantsProvider {
+        ArrayList<UriPermission> providePersistentUriGrants();
+    }
+}
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/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index c54d490..eafd9d7 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -97,6 +97,15 @@
                     USAGE_ALARM,
                     USAGE_COMMUNICATION_REQUEST));
 
+    /**
+     * Usage allowed for vibrations when {@link Settings.System#VIBRATE_ON} is disabled.
+     *
+     * <p>The only allowed usage is accessibility, which is applied when the user enables talkback.
+     * Other usages that must ignore this setting should use
+     * {@link VibrationAttributes#FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF}.
+     */
+    private static final int VIBRATE_ON_DISABLED_USAGE_ALLOWED = USAGE_ACCESSIBILITY;
+
     /** Listener for changes on vibration settings. */
     interface OnVibratorSettingsChanged {
         /** Callback triggered when any of the vibrator settings change. */
@@ -127,6 +136,8 @@
     private SparseIntArray mCurrentVibrationIntensities = new SparseIntArray();
     @GuardedBy("mLock")
     private boolean mBatterySaverMode;
+    @GuardedBy("mLock")
+    private boolean mVibrateOn;
 
     VibrationSettings(Context context, Handler handler) {
         this(context, handler, new VibrationConfig(context.getResources()));
@@ -168,7 +179,7 @@
         try {
             ActivityManager.getService().registerUidObserver(mUidObserver,
                     ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
-                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
+                    ActivityManager.PROCESS_STATE_UNKNOWN, mContext.getOpPackageName());
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
@@ -199,6 +210,7 @@
 
         // Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES));
+        registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_ON));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING));
         registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER));
         registerSettingsObserver(Settings.System.getUriFor(
@@ -314,11 +326,14 @@
                 return Vibration.Status.IGNORED_FOR_POWER;
             }
 
-            int intensity = getCurrentIntensity(usage);
-            if ((intensity == Vibrator.VIBRATION_INTENSITY_OFF)
-                    && !attrs.isFlagSet(
-                            VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
-                return Vibration.Status.IGNORED_FOR_SETTINGS;
+            if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
+                if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
+                    return Vibration.Status.IGNORED_FOR_SETTINGS;
+                }
+
+                if (getCurrentIntensity(usage) == Vibrator.VIBRATION_INTENSITY_OFF) {
+                    return Vibration.Status.IGNORED_FOR_SETTINGS;
+                }
             }
 
             if (!shouldVibrateForRingerModeLocked(usage)) {
@@ -357,6 +372,7 @@
     void updateSettings() {
         synchronized (mLock) {
             mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
+            mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
 
             int alarmIntensity = toIntensity(
                     loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
@@ -437,8 +453,9 @@
                     + "mVibratorConfig=" + mVibrationConfig
                     + ", mVibrateInputDevices=" + mVibrateInputDevices
                     + ", mBatterySaverMode=" + mBatterySaverMode
-                    + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
+                    + ", mVibrateOn=" + mVibrateOn
                     + ", mVibrationIntensities=" + vibrationIntensitiesString
+                    + ", mProcStatesCache=" + mUidObserver.mProcStatesCache
                     + '}';
         }
     }
@@ -446,6 +463,8 @@
     /** Write current settings into given {@link ProtoOutputStream}. */
     public void dumpProto(ProtoOutputStream proto) {
         synchronized (mLock) {
+            proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
+            proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
             proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY,
                     getCurrentIntensity(USAGE_ALARM));
             proto.write(VibratorManagerServiceDumpProto.ALARM_DEFAULT_INTENSITY,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 2ec42b4..ee2cc7b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -81,7 +81,9 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SELinux;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -92,6 +94,7 @@
 import android.service.wallpaper.WallpaperService;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -420,7 +423,7 @@
                 Slog.v(TAG, "notifyWallpaperColorsChangedOnDisplay " + which);
             }
 
-            needsExtraction = wallpaper.primaryColors == null;
+            needsExtraction = wallpaper.primaryColors == null || wallpaper.mIsColorExtractedFromDim;
         }
 
         if (needsExtraction) {
@@ -491,12 +494,17 @@
         String cropFile = null;
         boolean defaultImageWallpaper = false;
         int wallpaperId;
+        float dimAmount;
+
+        synchronized (mLock) {
+            wallpaper.mIsColorExtractedFromDim = false;
+        }
 
         if (wallpaper.equals(mFallbackWallpaper)) {
             synchronized (mLock) {
                 if (mFallbackWallpaper.primaryColors != null) return;
             }
-            final WallpaperColors colors = extractDefaultImageWallpaperColors();
+            final WallpaperColors colors = extractDefaultImageWallpaperColors(wallpaper);
             synchronized (mLock) {
                 mFallbackWallpaper.primaryColors = colors;
             }
@@ -513,18 +521,19 @@
                 defaultImageWallpaper = true;
             }
             wallpaperId = wallpaper.wallpaperId;
+            dimAmount = wallpaper.mWallpaperDimAmount;
         }
 
         WallpaperColors colors = null;
         if (cropFile != null) {
             Bitmap bitmap = BitmapFactory.decodeFile(cropFile);
             if (bitmap != null) {
-                colors = WallpaperColors.fromBitmap(bitmap);
+                colors = WallpaperColors.fromBitmap(bitmap, dimAmount);
                 bitmap.recycle();
             }
         } else if (defaultImageWallpaper) {
             // There is no crop and source file because this is default image wallpaper.
-            colors = extractDefaultImageWallpaperColors();
+            colors = extractDefaultImageWallpaperColors(wallpaper);
         }
 
         if (colors == null) {
@@ -544,11 +553,13 @@
         }
     }
 
-    private WallpaperColors extractDefaultImageWallpaperColors() {
+    private WallpaperColors extractDefaultImageWallpaperColors(WallpaperData wallpaper) {
         if (DEBUG) Slog.d(TAG, "Extract default image wallpaper colors");
+        float dimAmount;
 
         synchronized (mLock) {
             if (mCacheDefaultImageWallpaperColors != null) return mCacheDefaultImageWallpaperColors;
+            dimAmount = wallpaper.mWallpaperDimAmount;
         }
 
         WallpaperColors colors = null;
@@ -561,7 +572,7 @@
             final BitmapFactory.Options options = new BitmapFactory.Options();
             final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
             if (bitmap != null) {
-                colors = WallpaperColors.fromBitmap(bitmap);
+                colors = WallpaperColors.fromBitmap(bitmap, dimAmount);
                 bitmap.recycle();
             }
         } catch (OutOfMemoryError e) {
@@ -948,6 +959,23 @@
         WallpaperObserver wallpaperObserver;
 
         /**
+         * The dim amount to be applied to the wallpaper.
+         */
+        float mWallpaperDimAmount = 0.0f;
+
+        /**
+         * A map to keep track of the dimming set by different applications. The key is the calling
+         * UID and the value is the dim amount.
+         */
+        ArrayMap<Integer, Float> mUidToDimAmount = new ArrayMap<>();
+
+        /**
+         * Whether we need to extract the wallpaper colors again to calculate the dark hints
+         * after dimming is applied.
+         */
+        boolean mIsColorExtractedFromDim;
+
+        /**
          * List of callbacks registered they should each be notified when the wallpaper is changed.
          */
         private RemoteCallbackList<IWallpaperManagerCallback> callbacks
@@ -1487,6 +1515,15 @@
                         Slog.w(TAG, "Failed to register local colors areas", e);
                     }
                 }
+
+                if (mWallpaper.mWallpaperDimAmount != 0f) {
+                    try {
+                        connector.mEngine.applyDimming(mWallpaper.mWallpaperDimAmount);
+                        notifyWallpaperColorsChanged(mWallpaper, FLAG_SYSTEM);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to dim wallpaper", e);
+                    }
+                }
             }
         }
 
@@ -2536,6 +2573,98 @@
         if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas);
     }
 
+    /**
+     * Returns true if the lock screen wallpaper exists (different wallpaper from the system)
+     */
+    @Override
+    public boolean lockScreenWallpaperExists() {
+        synchronized (mLock) {
+            return mLockWallpaperMap.get(mCurrentUserId) != null;
+        }
+    }
+
+    /**
+     * Sets wallpaper dim amount for the calling UID. This only applies to FLAG_SYSTEM wallpaper as
+     * the lock screen does not have a wallpaper component, so we use mWallpaperMap.
+     *
+     * @param dimAmount Dim amount which would be blended with the system default dimming.
+     */
+    @Override
+    public void setWallpaperDimAmount(float dimAmount) throws RemoteException {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT);
+        int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+
+                if (dimAmount == 0.0f) {
+                    wallpaper.mUidToDimAmount.remove(uid);
+                } else {
+                    wallpaper.mUidToDimAmount.put(uid, dimAmount);
+                }
+
+                float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+                wallpaper.mWallpaperDimAmount = maxDimAmount;
+                // Also set the dim amount to the lock screen wallpaper if the lock and home screen
+                // do not share the same wallpaper
+                if (lockWallpaper != null) {
+                    lockWallpaper.mWallpaperDimAmount = maxDimAmount;
+                }
+
+                if (wallpaper.connection != null) {
+                    wallpaper.connection.forEachDisplayConnector(connector -> {
+                        if (connector.mEngine != null) {
+                            try {
+                                connector.mEngine.applyDimming(maxDimAmount);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG,
+                                        "Can't apply dimming on wallpaper display connector", e);
+                            }
+                        }
+                    });
+                    // Need to extract colors again to re-calculate dark hints after
+                    // applying dimming.
+                    wallpaper.mIsColorExtractedFromDim = true;
+                    notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+                    if (lockWallpaper != null) {
+                        lockWallpaper.mIsColorExtractedFromDim = true;
+                        notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
+                    }
+                    saveSettingsLocked(wallpaper.userId);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public float getWallpaperDimAmount() {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT);
+        synchronized (mLock) {
+            WallpaperData data = mWallpaperMap.get(mCurrentUserId);
+            return data.mWallpaperDimAmount;
+        }
+    }
+
+    /**
+     * Gets the highest dim amount among all the calling UIDs that set the wallpaper dim amount.
+     * Return 0f as default value to indicate no application has dimmed the wallpaper.
+     *
+     * @param uidToDimAmountMap Map of UIDs to dim amounts
+     */
+    private float getHighestDimAmountFromMap(ArrayMap<Integer, Float> uidToDimAmountMap) {
+        float maxDimAmount = 0.0f;
+        for (Map.Entry<Integer, Float> entry : uidToDimAmountMap.entrySet()) {
+            if (entry.getValue() > maxDimAmount) {
+                maxDimAmount = entry.getValue();
+            }
+        }
+        return maxDimAmount;
+    }
+
     @Override
     public WallpaperColors getWallpaperColors(int which, int userId, int displayId)
             throws RemoteException {
@@ -2562,7 +2691,8 @@
             if (wallpaperData == null) {
                 return null;
             }
-            shouldExtract = wallpaperData.primaryColors == null;
+            shouldExtract = wallpaperData.primaryColors == null
+                    || wallpaperData.mIsColorExtractedFromDim;
         }
 
         if (shouldExtract) {
@@ -2664,6 +2794,7 @@
         lockWP.cropHint.set(sysWP.cropHint);
         lockWP.allowBackup = sysWP.allowBackup;
         lockWP.primaryColors = sysWP.primaryColors;
+        lockWP.mWallpaperDimAmount = sysWP.mWallpaperDimAmount;
 
         // Migrate the bitmap files outright; no need to copy
         try {
@@ -3191,6 +3322,18 @@
             out.attributeInt(null, "paddingBottom", wpdData.mPadding.bottom);
         }
 
+        out.attributeFloat(null, "dimAmount", wallpaper.mWallpaperDimAmount);
+        int dimAmountsCount = wallpaper.mUidToDimAmount.size();
+        out.attributeInt(null, "dimAmountsCount", dimAmountsCount);
+        if (dimAmountsCount > 0) {
+            int index = 0;
+            for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
+                out.attributeInt(null, "dimUID" + index, entry.getKey());
+                out.attributeFloat(null, "dimValue" + index, entry.getValue());
+                index++;
+            }
+        }
+
         if (wallpaper.primaryColors != null) {
             int colorsCount = wallpaper.primaryColors.getMainColors().size();
             out.attributeInt(null, "colorsCount", colorsCount);
@@ -3267,6 +3410,10 @@
         return parser.getAttributeInt(null, name, defValue);
     }
 
+    private float getAttributeFloat(TypedXmlPullParser parser, String name, float defValue) {
+        return parser.getAttributeFloat(null, name, defValue);
+    }
+
     /**
      * Sometimes it is expected the wallpaper map may not have a user's data.  E.g. This could
      * happen during user switch.  The async user switch observer may not have received
@@ -3471,6 +3618,17 @@
         wpData.mPadding.top = getAttributeInt(parser, "paddingTop", 0);
         wpData.mPadding.right = getAttributeInt(parser, "paddingRight", 0);
         wpData.mPadding.bottom = getAttributeInt(parser, "paddingBottom", 0);
+        wallpaper.mWallpaperDimAmount = getAttributeFloat(parser, "dimAmount", 0f);
+        int dimAmountsCount = getAttributeInt(parser, "dimAmountsCount", 0);
+        if (dimAmountsCount > 0) {
+            ArrayMap<Integer, Float> allDimAmounts = new ArrayMap<>(dimAmountsCount);
+            for (int i = 0; i < dimAmountsCount; i++) {
+                int uid = getAttributeInt(parser, "dimUID" + i, 0);
+                float dimValue = getAttributeFloat(parser, "dimValue" + i, 0f);
+                allDimAmounts.put(uid, dimValue);
+            }
+            wallpaper.mUidToDimAmount = allDimAmounts;
+        }
         int colorsCount = getAttributeInt(parser, "colorsCount", 0);
         int allColorsCount =  getAttributeInt(parser, "allColorsCount", 0);
         if (allColorsCount > 0) {
@@ -3637,6 +3795,14 @@
         return false;
     }
 
+    @Override // Binder call
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        new WallpaperManagerShellCommand(WallpaperManagerService.this).exec(this, in, out, err,
+                args, callback, resultReceiver);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3664,6 +3830,13 @@
                 pw.print("  mName=");  pw.println(wallpaper.name);
                 pw.print("  mAllowBackup="); pw.println(wallpaper.allowBackup);
                 pw.print("  mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
+                pw.print("  mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
+                pw.print("  isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim);
+                pw.println("  mUidToDimAmount:");
+                for (Map.Entry<Integer, Float> entry : wallpaper.mUidToDimAmount.entrySet()) {
+                    pw.print("    UID="); pw.print(entry.getKey());
+                    pw.print(" dimAmount="); pw.println(entry.getValue());
+                }
                 if (wallpaper.connection != null) {
                     WallpaperConnection conn = wallpaper.connection;
                     pw.print("  Wallpaper connection ");
@@ -3695,6 +3868,7 @@
                 pw.print("  mCropHint="); pw.println(wallpaper.cropHint);
                 pw.print("  mName=");  pw.println(wallpaper.name);
                 pw.print("  mAllowBackup="); pw.println(wallpaper.allowBackup);
+                pw.print("  mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount);
             }
             pw.println("Fallback wallpaper state:");
             pw.print(" User "); pw.print(mFallbackWallpaper.userId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java
new file mode 100644
index 0000000..fc827b4
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerShellCommand.java
@@ -0,0 +1,95 @@
+/*
+ * 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.wallpaper;
+
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell Command class to run adb commands on the wallpaper service
+ */
+public class WallpaperManagerShellCommand extends ShellCommand {
+    private static final String TAG = "WallpaperManagerShellCommand";
+
+    private final WallpaperManagerService mService;
+
+    public WallpaperManagerShellCommand(WallpaperManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            onHelp();
+            return 1;
+        }
+        switch(cmd) {
+            case "set-dim-amount":
+                return setWallpaperDimAmount();
+            case "get-dim-amount":
+                return getWallpaperDimAmount();
+            case "-h":
+            case "help":
+                onHelp();
+                return 0;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Wallpaper manager commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  set-dim-amount DIMMING");
+        pw.println("    Sets the current dimming value to DIMMING (a number between 0 and 1).");
+        pw.println();
+        pw.println("  get-dim-amount");
+        pw.println("    Get the current wallpaper dim amount.");
+    }
+
+    /**
+     * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
+     * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
+     */
+    private int setWallpaperDimAmount() {
+        float dimAmount = Float.parseFloat(getNextArgRequired());
+        try {
+            mService.setWallpaperDimAmount(dimAmount);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't set wallpaper dim amount");
+        }
+        getOutPrintWriter().println("Dimming the wallpaper to: " + dimAmount);
+        return 0;
+    }
+
+    /**
+     * Gets the current additional dim amount set on the wallpaper. 0f means no application has
+     * added any dimming on top of the system default dim amount.
+     */
+    private int getWallpaperDimAmount() {
+        float dimAmount = mService.getWallpaperDimAmount();
+        getOutPrintWriter().println("The current wallpaper dim amount is: " + dimAmount);
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 86ef8d2..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() {
@@ -7364,12 +7366,17 @@
 
     @VisibleForTesting
     void clearSizeCompatMode() {
+        final float lastSizeCompatScale = mSizeCompatScale;
         mInSizeCompatModeForBounds = false;
         mSizeCompatScale = 1f;
         mSizeCompatBounds = null;
         mCompatDisplayInsets = null;
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+        }
 
-        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+        // Clear config override in #updateCompatDisplayInsets().
+        onRequestedOverrideConfigurationChanged(EMPTY);
     }
 
     @Override
@@ -7924,6 +7931,7 @@
         final int contentH = resolvedAppBounds.height();
         final int viewportW = containerAppBounds.width();
         final int viewportH = containerAppBounds.height();
+        final float lastSizeCompatScale = mSizeCompatScale;
         // Only allow to scale down.
         mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
                 ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
@@ -7942,6 +7950,9 @@
         } else {
             mSizeCompatBounds = null;
         }
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
+        }
 
         // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal
         // decor if needed. Horizontal position is adjusted in
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 4b3078ae..316bf20 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -22,7 +22,6 @@
 
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Overridable;
 import android.os.IBinder;
 import android.os.InputConstants;
 import android.os.Looper;
@@ -47,7 +46,6 @@
      * Feature flag for making Activities consume all touches within their task bounds.
      */
     @ChangeId
-    @Overridable
     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
 
     private static final String TAG = "ActivityRecordInputSink";
@@ -116,7 +114,7 @@
     private InputWindowHandle createInputWindowHandle() {
         InputWindowHandle inputWindowHandle = new InputWindowHandle(null,
                 mActivityRecord.getDisplayId());
-        inputWindowHandle.replaceTouchableRegionWithCrop(mActivityRecord.getSurfaceControl());
+        inputWindowHandle.replaceTouchableRegionWithCrop = true;
         inputWindowHandle.name = mName;
         inputWindowHandle.ownerUid = Process.myUid();
         inputWindowHandle.ownerPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ddd624d..ed9dcef 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -221,10 +221,10 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
-import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
+import android.window.BackNavigationInfo;
 import android.window.IWindowOrganizerController;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.TaskSnapshot;
@@ -458,7 +458,7 @@
     private final ClientLifecycleManager mLifecycleManager;
 
     @Nullable
-    private final BackGestureController mBackGestureController;
+    private final BackNavigationController mBackNavigationController;
 
     private TaskChangeNotificationController mTaskChangeNotificationController;
     /** The controller for all operations related to locktask. */
@@ -836,8 +836,6 @@
         mSystemThread = ActivityThread.currentActivityThread();
         mUiContext = mSystemThread.getSystemUiContext();
         mLifecycleManager = new ClientLifecycleManager();
-        mBackGestureController = BackGestureController.isEnabled() ? new BackGestureController()
-                : null;
         mVisibleActivityProcessTracker = new VisibleActivityProcessTracker(this);
         mInternal = new LocalService();
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
@@ -845,6 +843,8 @@
         mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
         mTaskFragmentOrganizerController =
                 mWindowOrganizerController.mTaskFragmentOrganizerController;
+        mBackNavigationController = BackNavigationController.isEnabled()
+                ? new BackNavigationController() : null;
     }
 
     public void onSystemReady() {
@@ -1022,6 +1022,9 @@
             mLockTaskController.setWindowManager(wm);
             mTaskSupervisor.setWindowManager(wm);
             mRootWindowContainer.setWindowManager(wm);
+            if (mBackNavigationController != null) {
+                mBackNavigationController.setTaskSnapshotController(wm.mTaskSnapshotController);
+            }
         }
     }
 
@@ -1768,11 +1771,13 @@
     }
 
     @Override
-    public void startBackPreview(IRemoteAnimationRunner runner) {
-        if (mBackGestureController == null) {
-            return;
+    public BackNavigationInfo startBackNavigation() {
+        mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
+                "startBackNavigation()");
+        if (mBackNavigationController == null) {
+            return null;
         }
-        mBackGestureController.startBackPreview();
+        return mBackNavigationController.startBackNavigation(getTopDisplayFocusedRootTask());
     }
 
     /**
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/BackGestureController.java b/services/core/java/com/android/server/wm/BackGestureController.java
deleted file mode 100644
index f8f6254..0000000
--- a/services/core/java/com/android/server/wm/BackGestureController.java
+++ /dev/null
@@ -1,37 +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.server.wm;
-
-import android.os.SystemProperties;
-
-/**
- * Controller to handle actions related to the back gesture on the server side.
- */
-public class BackGestureController {
-
-    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
-
-    public static boolean isEnabled() {
-        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
-    }
-
-    /**
-     * Start a remote animation the back gesture.
-     */
-    public void startBackPreview() {
-    }
-}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
new file mode 100644
index 0000000..a8779fa
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -0,0 +1,240 @@
+/*
+ * 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.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.BackNavigationInfo;
+import android.window.TaskSnapshot;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+/**
+ * Controller to handle actions related to the back gesture on the server side.
+ */
+class BackNavigationController {
+
+    private static final String TAG = "BackNavigationController";
+    private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
+
+    @Nullable
+    private TaskSnapshotController mTaskSnapshotController;
+
+    /**
+     * Returns true if the back predictability feature is enabled
+     */
+    static boolean isEnabled() {
+        return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
+    }
+
+    /**
+     * Set up the necessary leashes and build a {@link BackNavigationInfo} instance for an upcoming
+     * back gesture animation.
+     *
+     * @param task the currently focused {@link Task}.
+     * @return a {@link BackNavigationInfo} instance containing the required leashes and metadata
+     * for the animation.
+     */
+    @Nullable
+    BackNavigationInfo startBackNavigation(@NonNull Task task) {
+        return startBackNavigation(task, null);
+    }
+
+    /**
+     * @param tx, a transaction to be used for the attaching the animation leash.
+     *            This is used in tests. If null, the object will be initialized with a new {@link
+     *            android.view.SurfaceControl.Transaction}
+     * @see #startBackNavigation(Task)
+     */
+    @VisibleForTesting
+    @Nullable
+    BackNavigationInfo startBackNavigation(@NonNull Task task,
+            @Nullable SurfaceControl.Transaction tx) {
+
+        if (tx == null) {
+            tx = new SurfaceControl.Transaction();
+        }
+
+        int backType = BackNavigationInfo.TYPE_UNDEFINED;
+        Task prevTask = task;
+        ActivityRecord prev;
+        WindowContainer<?> removedWindowContainer;
+        ActivityRecord activityRecord;
+        SurfaceControl animationLeashParent;
+        WindowConfiguration taskWindowConfiguration;
+        SurfaceControl animLeash;
+        HardwareBuffer screenshotBuffer = null;
+        int prevTaskId;
+        int prevUserId;
+
+        synchronized (task.mWmService.mGlobalLock) {
+            activityRecord = task.topRunningActivity();
+            removedWindowContainer = activityRecord;
+            taskWindowConfiguration = task.getTaskInfo().configuration.windowConfiguration;
+
+            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation task=%s, topRunningActivity=%s",
+                    task, activityRecord);
+
+            // IME is visible, back gesture will dismiss it, nothing to preview.
+            if (task.getDisplayContent().getImeContainer().isVisible()) {
+                return null;
+            }
+
+            // Current Activity is home, there is no previous activity to display
+            if (activityRecord.isActivityTypeHome()) {
+                return null;
+            }
+
+            prev = task.getActivity(
+                    (r) -> !r.finishing && r.getTask() == task && !r.isTopRunningActivity());
+
+            if (prev != null) {
+                backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+            } else if (task.returnsToHomeRootTask()) {
+                prevTask = null;
+                removedWindowContainer = task;
+                backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+            } else if (activityRecord.isRootOfTask()) {
+                // TODO(208789724): Create single source of truth for this, maybe in
+                //  RootWindowContainer
+                // TODO: Also check Task.shouldUpRecreateTaskLocked() for prev logic
+                prevTask = task.mRootWindowContainer.getTaskBelow(task);
+                removedWindowContainer = task;
+                if (prevTask.isActivityTypeHome()) {
+                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                } else {
+                    prev = prevTask.getTopNonFinishingActivity();
+                    backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                }
+            }
+
+            prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
+            prevUserId = prevTask != null ? prevTask.mUserId : 0;
+
+            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Activity is %s",
+                    prev != null ? prev.mActivityComponent : null);
+
+            //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is
+            // implemented. For now we simply have the mBackScreenshots hash map that dumbly
+            // saves the screenshots.
+            if (needsScreenshot(backType) && prev != null && prev.mActivityComponent != null) {
+                screenshotBuffer = getActivitySnapshot(task, prev.mActivityComponent);
+            }
+
+            // Prepare a leash to animate the current top window
+            animLeash = removedWindowContainer.makeAnimationLeash()
+                    .setName("BackPreview Leash")
+                    .setHidden(false)
+                    .setBLASTLayer()
+                    .build();
+            removedWindowContainer.reparentSurfaceControl(tx, animLeash);
+
+            animationLeashParent = removedWindowContainer.getAnimationLeashParent();
+        }
+
+        SurfaceControl.Builder builder = new SurfaceControl.Builder()
+                .setName("BackPreview Screenshot")
+                .setParent(animationLeashParent)
+                .setHidden(false)
+                .setBLASTLayer();
+        SurfaceControl screenshotSurface = builder.build();
+
+        // Find a screenshot of the previous activity
+
+        if (needsScreenshot(backType) && prevTask != null) {
+            if (screenshotBuffer == null) {
+                screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
+            }
+        }
+        tx.apply();
+
+        WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
+        try {
+            activityRecord.token.linkToDeath(
+                    () -> resetSurfaces(finalRemovedWindowContainer), 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to link to death", e);
+            resetSurfaces(removedWindowContainer);
+            return null;
+        }
+
+        return new BackNavigationInfo(backType,
+                animLeash,
+                screenshotSurface,
+                screenshotBuffer,
+                taskWindowConfiguration,
+                new RemoteCallback(result -> resetSurfaces(finalRemovedWindowContainer
+                )));
+    }
+
+
+    private HardwareBuffer getActivitySnapshot(@NonNull Task task,
+            ComponentName activityComponent) {
+        // Check if we have a screenshot of the previous activity, indexed by its
+        // component name.
+        SurfaceControl.ScreenshotHardwareBuffer backBuffer = task.mBackScreenshots
+                .get(activityComponent.flattenToString());
+        return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
+
+    }
+
+    private HardwareBuffer getTaskSnapshot(int taskId, int userId) {
+        if (mTaskSnapshotController == null) {
+            return null;
+        }
+        TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(taskId,
+                userId, true /* restoreFromDisk */, false  /* isLowResolution */);
+        return snapshot != null ? snapshot.getHardwareBuffer() : null;
+    }
+
+    private boolean needsScreenshot(int backType) {
+        switch (backType) {
+            case BackNavigationInfo.TYPE_RETURN_TO_HOME:
+            case BackNavigationInfo.TYPE_DIALOG_CLOSE:
+                return false;
+        }
+        return true;
+    }
+
+    private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) {
+        synchronized (windowContainer.mWmService.mGlobalLock) {
+            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces");
+            SurfaceControl.Transaction tx = windowContainer.getSyncTransaction();
+            SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
+            if (surfaceControl != null) {
+                tx.reparent(surfaceControl,
+                        windowContainer.getParent().getSurfaceControl());
+                tx.apply();
+            }
+        }
+    }
+
+    void setTaskSnapshotController(@Nullable TaskSnapshotController taskSnapshotController) {
+        mTaskSnapshotController = taskSnapshotController;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c65ca08..5facc0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -122,6 +122,8 @@
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
 import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
 import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
+import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
+import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
 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;
@@ -169,6 +171,7 @@
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
 import android.graphics.Insets;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -191,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;
@@ -236,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;
 
@@ -321,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
@@ -545,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();
@@ -576,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.
      */
@@ -838,7 +843,6 @@
             }
             w.mSurfacePlacementNeeded = true;
             w.mLayoutNeeded = false;
-            w.prelayout();
             final boolean firstLayout = !w.isLaidOut();
             getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
             w.mLayoutSeq = mLayoutSeq;
@@ -881,7 +885,6 @@
             }
             w.mSurfacePlacementNeeded = true;
             w.mLayoutNeeded = false;
-            w.prelayout();
             getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
             w.mLayoutSeq = mLayoutSeq;
             if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
@@ -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);
     }
 
@@ -2001,6 +2017,7 @@
     }
 
     void configureDisplayPolicy() {
+        mRootWindowContainer.updateDisplayImePolicyCache();
         mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
         mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight);
     }
@@ -2076,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(
@@ -2710,6 +2719,7 @@
             //                    layout.
             mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChanged */);
         }
+        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -2718,6 +2728,7 @@
     void onDisplayChanged(DisplayContent dc) {
         super.onDisplayChanged(dc);
         updateSystemGestureExclusionLimit();
+        updateKeepClearAreas();
     }
 
     void updateSystemGestureExclusionLimit() {
@@ -3204,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();
         }
     }
 
@@ -3226,7 +3237,7 @@
             }
             if (!controller.isCollecting(this)) {
                 controller.collect(this);
-                startFadeRotationAnimationIfNeeded();
+                startAsyncRotationIfNeeded();
             }
             return;
         }
@@ -3238,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);
         }
@@ -3270,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 {
@@ -3327,6 +3339,9 @@
             }
         }
         proto.write(IME_POLICY, getImePolicy());
+        for (Rect r : getKeepClearAreas()) {
+            r.dumpDebug(proto, KEEP_CLEAR_AREAS);
+        }
         proto.end(token);
     }
 
@@ -3344,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) {
@@ -3386,6 +3402,13 @@
             pw.println(mSystemGestureExclusion);
         }
 
+        final List<Rect> keepClearAreas = getKeepClearAreas();
+        if (!keepClearAreas.isEmpty()) {
+            pw.println();
+            pw.print("  keepClearAreas=");
+            pw.println(keepClearAreas);
+        }
+
         pw.println();
         pw.println(prefix + "Display areas in top down Z order:");
         dumpChildDisplayArea(pw, subPrefix, dumpAll);
@@ -3613,6 +3636,7 @@
         }
 
         adjustForImeIfNeeded();
+        updateKeepClearAreas();
 
         // We may need to schedule some toast windows to be removed. The toasts for an app that
         // does not have input focus are removed within a timeout to prevent apps to redress
@@ -3939,6 +3963,9 @@
         }
     }
 
+    // IMPORTANT: When introducing new dependencies in this method, make sure that
+    // changes to those result in RootWindowContainer.updateDisplayImePolicyCache()
+    // being called.
     @DisplayImePolicy int getImePolicy() {
         if (!isTrusted()) {
             return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -3967,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);
             }
         }
     }
@@ -3987,11 +4014,12 @@
         if (target == mImeLayeringTarget) {
             return;
         }
-        // Prepare the IME screenshot for the last IME target when its task is applying app
-        // transition. This is for the better IME transition to keep IME visibility when
-        // transitioning to the next task.
+        // If the IME target is the input target, before it changes, prepare the IME screenshot
+        // for the last IME target when its task is applying app transition. This is for the
+        // better IME transition to keep IME visibility when transitioning to the next task.
         if (mImeLayeringTarget != null && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+                && mImeLayeringTarget == mImeInputTarget) {
             attachAndShowImeScreenshotOnTarget();
         }
 
@@ -5477,19 +5505,28 @@
         mSystemGestureExclusionListeners.unregister(listener);
     }
 
+    void updateKeepClearAreas() {
+        mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged(
+                this, getKeepClearAreas());
+    }
+
     /**
-     * @see IWindowManager#setForwardedInsets
+     * Returns all keep-clear areas from visible windows on this display.
      */
-    public void setForwardedInsets(Insets insets) {
-        if (insets == null) {
-            insets = Insets.NONE;
-        }
-        if (mDisplayPolicy.getForwardedInsets().equals(insets)) {
-            return;
-        }
-        mDisplayPolicy.setForwardedInsets(insets);
-        setLayoutNeeded();
-        mWmService.mWindowPlacerLocked.requestTraversal();
+    ArrayList<Rect> getKeepClearAreas() {
+        final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>();
+        final Matrix tmpMatrix = new Matrix();
+        final float[] tmpFloat9 = new float[9];
+        forAllWindows(w -> {
+            if (w.isVisible() && !w.inPinnedWindowingMode()) {
+                keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9));
+            }
+
+            // We stop traversing when we reach the base of a fullscreen app.
+            return w.getWindowType() == TYPE_BASE_APPLICATION
+                    && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+        }, true);
+        return keepClearAreas;
     }
 
     protected MetricsLogger getMetricsLogger() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0745b3b..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();
@@ -359,16 +360,6 @@
 
     private int mDisplayCutoutTouchableRegionSize;
 
-    /**
-     * The area covered by system windows which belong to another display. Forwarded insets is set
-     * in case this is a virtual display, this is displayed on another display that has insets, and
-     * the bounds of this display is overlapping with the insets of the host display (e.g. IME is
-     * displayed on the host display, and it covers a part of this virtual display.)
-     * The forwarded insets is used to compute display frames of this virtual display, which will
-     * be then used to layout windows in the virtual display.
-     */
-    @NonNull private Insets mForwardedInsets = Insets.NONE;
-
     private RefreshRatePolicy mRefreshRatePolicy;
 
     /**
@@ -1442,33 +1433,6 @@
         return mForceShowSystemBars;
     }
 
-    // TODO: Should probably be moved into DisplayFrames.
-    /**
-     * Return the layout hints for a newly added window. These values are computed on the
-     * most recent layout, so they are not guaranteed to be correct.
-     *
-     * @param attrs The LayoutParams of the window.
-     * @param windowToken The token of the window.
-     * @param outInsetsState The insets state of this display from the client's perspective.
-     * @param localClient Whether the client is from the our process.
-     * @return Whether to always consume the system bars.
-     *         See {@link #areSystemBarsForcedShownLw()}.
-     */
-    boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState,
-            boolean localClient) {
-        final InsetsState state =
-                mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
-        final boolean hasCompatScale = WindowState.hasCompatScale(attrs, windowToken);
-        outInsetsState.set(state, hasCompatScale || localClient);
-        if (hasCompatScale) {
-            final float compatScale = windowToken != null
-                    ? windowToken.getSizeCompatScale()
-                    : mDisplayContent.mCompatibleScreenScale;
-            outInsetsState.scale(1f / compatScale);
-        }
-        return mForceShowSystemBars;
-    }
-
     /**
      * Computes the frames of display (its logical size, rotation and cutout should already be set)
      * used to layout window. This method only changes the given display frames, insets state and
@@ -1563,7 +1527,7 @@
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
         mNavBarBackgroundWindow = null;
-        mStatusBarColorWindows.clear();
+        mStatusBarAppearanceRegionList.clear();
         mStatusBarBackgroundWindows.clear();
         mStatusBarColorCheckedBounds.setEmpty();
         mStatusBarBackgroundCheckedBounds.setEmpty();
@@ -1643,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);
                 }
             }
@@ -1663,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;
@@ -1677,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
@@ -2149,18 +2154,6 @@
         }
     }
 
-    /**
-     * @see IWindowManager#setForwardedInsets
-     */
-    public void setForwardedInsets(@NonNull Insets forwardedInsets) {
-        mForwardedInsets = forwardedInsets;
-    }
-
-    @NonNull
-    public Insets getForwardedInsets() {
-        return mForwardedInsets;
-    }
-
     @NavigationBarPosition
     int navigationBarPosition(int displayRotation) {
         if (mNavigationBar != null) {
@@ -2319,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();
@@ -2338,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
@@ -2353,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();
@@ -2760,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/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 4141090..276dbe9 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -17,11 +17,14 @@
 package com.android.server.wm;
 
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.IntArray;
 import android.view.IDisplayWindowListener;
 
+import java.util.List;
+
 /**
  * Manages dispatch of relevant hierarchy changes to interested listeners. Listeners are assumed
  * to be remote.
@@ -116,4 +119,16 @@
         }
         mDisplayListeners.finishBroadcast();
     }
+
+    void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) {
+        int count = mDisplayListeners.beginBroadcast();
+        for (int i = 0; i < count; ++i) {
+            try {
+                mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged(
+                        display.mDisplayId, keepClearAreas);
+            } catch (RemoteException e) {
+            }
+        }
+        mDisplayListeners.finishBroadcast();
+    }
 }
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/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 0e2d847..e04e3a3 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -41,6 +41,8 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmbeddedWindowController" : TAG_WM;
     /* maps input token to an embedded window */
     private ArrayMap<IBinder /*input token */, EmbeddedWindow> mWindows = new ArrayMap<>();
+    private ArrayMap<IBinder /*focus grant token */, EmbeddedWindow> mWindowsByFocusToken =
+        new ArrayMap<>();
     private final Object mGlobalLock;
     private final ActivityTaskManagerService mAtmService;
 
@@ -59,10 +61,13 @@
     void add(IBinder inputToken, EmbeddedWindow window) {
         try {
             mWindows.put(inputToken, window);
+            final IBinder focusToken = window.getFocusGrantToken();
+            mWindowsByFocusToken.put(focusToken, window);
             updateProcessController(window);
             window.mClient.asBinder().linkToDeath(()-> {
                 synchronized (mGlobalLock) {
                     mWindows.remove(inputToken);
+                    mWindowsByFocusToken.remove(focusToken);
                 }
             }, 0);
         } catch (RemoteException e) {
@@ -107,8 +112,10 @@
 
     void remove(IWindow client) {
         for (int i = mWindows.size() - 1; i >= 0; i--) {
-            if (mWindows.valueAt(i).mClient.asBinder() == client.asBinder()) {
+            EmbeddedWindow ew = mWindows.valueAt(i);
+            if (ew.mClient.asBinder() == client.asBinder()) {
                 mWindows.removeAt(i).onRemoved();
+                mWindowsByFocusToken.remove(ew.getFocusGrantToken());
                 return;
             }
         }
@@ -116,8 +123,10 @@
 
     void onWindowRemoved(WindowState host) {
         for (int i = mWindows.size() - 1; i >= 0; i--) {
-            if (mWindows.valueAt(i).mHostWindowState == host) {
+            EmbeddedWindow ew = mWindows.valueAt(i);
+            if (ew.mHostWindowState == host) {
                 mWindows.removeAt(i).onRemoved();
+                mWindowsByFocusToken.remove(ew.getFocusGrantToken());
             }
         }
     }
@@ -126,6 +135,10 @@
         return mWindows.get(inputToken);
     }
 
+    EmbeddedWindow getByFocusToken(IBinder focusGrantToken) {
+        return mWindowsByFocusToken.get(focusGrantToken);
+    }
+
     void onActivityRemoved(ActivityRecord activityRecord) {
         for (int i = mWindows.size() - 1; i >= 0; i--) {
             final EmbeddedWindow window = mWindows.valueAt(i);
@@ -157,6 +170,8 @@
         // and this variable is mostly used for tracking that.
         boolean mIsOverlay = false;
 
+        private IBinder mFocusGrantToken;
+
         /**
          * @param session  calling session to check ownership of the window
          * @param clientToken client token used to clean up the map if the embedding process dies
@@ -171,7 +186,7 @@
          */
         EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
                        WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
-                       int displayId) {
+                       int displayId, IBinder focusGrantToken) {
             mSession = session;
             mWmService = service;
             mClient = clientToken;
@@ -182,6 +197,7 @@
             mOwnerPid = ownerPid;
             mWindowType = windowType;
             mDisplayId = displayId;
+            mFocusGrantToken = focusGrantToken;
         }
 
         @Override
@@ -242,6 +258,17 @@
             return mIsOverlay;
         }
 
+        IBinder getFocusGrantToken() {
+            return mFocusGrantToken;
+        }
+
+        IBinder getInputChannelToken() {
+            if (mInputChannel != null) {
+                return mInputChannel.getToken();
+            }
+            return null;
+        }
+
         /**
          * System hosted overlays need the WM to invoke grantEmbeddedWindowFocus and
          * so we need to participate inside handlePointerDownOutsideFocus logic
@@ -255,7 +282,7 @@
 
         private void handleTap(boolean grantFocus) {
             if (mInputChannel != null) {
-                mWmService.grantEmbeddedWindowFocus(mSession, mInputChannel.getToken(), grantFocus);
+                mWmService.grantEmbeddedWindowFocus(mSession, mFocusGrantToken, grantFocus);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 963f326..8d3e071 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -97,7 +97,7 @@
         // activities are actually behind other fullscreen activities, but still required
         // to be visible (such as performing Recents animation).
         final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
-                && mTaskFragment.isTopActivityFocusable()
+                && mTaskFragment.canBeResumed(starting)
                 && (starting == null || !starting.isDescendantOf(mTaskFragment));
 
         ArrayList<TaskFragment> adjacentTaskFragments = null;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index e02e7c5..f91969b 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -24,6 +24,7 @@
 import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
 import android.annotation.NonNull;
+import android.graphics.PointF;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -219,6 +220,11 @@
     }
 
     @Override
+    public PointF getCursorPosition() {
+        return mService.getLatestMousePosition();
+    }
+
+    @Override
     public void onPointerDownOutsideFocus(IBinder touchedToken) {
         mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
     }
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/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 1a1101e..a1468cc 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -366,6 +366,7 @@
         if (changed) {
             notifyInsetsChanged();
             mDisplayContent.updateSystemGestureExclusion();
+            mDisplayContent.updateKeepClearAreas();
             mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
         }
     }
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 5a420ca..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;
@@ -165,6 +162,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -986,6 +984,7 @@
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
+            dc.updateKeepClearAreas();
             dc.updateTouchExcludeRegion();
         });
 
@@ -1218,11 +1217,6 @@
         pw.println(mTopFocusedDisplayId);
     }
 
-    void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
-        pw.print("  mDefaultMinSizeOfResizeableTaskDp=");
-        pw.println(mDefaultMinSizeOfResizeableTaskDp);
-    }
-
     void dumpLayoutNeededDisplayIds(PrintWriter pw) {
         if (!isLayoutNeeded()) {
             return;
@@ -1269,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);
     }
 
@@ -1357,7 +1350,6 @@
                 mDefaultDisplay = displayContent;
             }
         }
-        calculateDefaultMinimalSizeOfResizeableTasks();
 
         final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
         defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
@@ -2530,9 +2522,16 @@
             // Drop any cached DisplayInfos associated with this display id - the values are now
             // out of date given this display changed event.
             mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId);
+            updateDisplayImePolicyCache();
         }
     }
 
+    void updateDisplayImePolicyCache() {
+        ArrayMap<Integer, Integer> displayImePolicyMap = new ArrayMap<>();
+        forAllDisplays(dc -> displayImePolicyMap.put(dc.getDisplayId(), dc.getImePolicy()));
+        mWmService.mDisplayImePolicyCache = Collections.unmodifiableMap(displayImePolicyMap);
+    }
+
     /** Update lists of UIDs that are present on displays and have access to them. */
     void updateUIDsPresentOnDisplay() {
         mDisplayAccessUIDs.clear();
@@ -3468,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.
@@ -3665,7 +3653,8 @@
 
             try {
                 if (mTaskSupervisor.realStartActivityLocked(r, mApp,
-                        mTop == r && r.isFocusable() /* andResume */, true /* checkConfig */)) {
+                        mTop == r && r.getTask().canBeResumed(r) /* andResume */,
+                        true /* checkConfig */)) {
                     mHasActivityStarted = true;
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 005544b..98acc46 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;
@@ -73,6 +72,7 @@
 import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.window.ClientWindowFrames;
+import android.window.IOnBackInvokedCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -221,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());
@@ -251,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);
@@ -490,6 +495,16 @@
         }
     }
 
+    @Override
+    public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mService.reportKeepClearAreasChanged(this, window, keepClearAreas);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private void actionOnWallpaper(IBinder window,
             BiConsumer<WallpaperController, WindowState> action) {
         final WindowState windowState = mService.windowForClientLocked(this, window, true);
@@ -798,7 +813,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
             IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
-            InputChannel outInputChannel) {
+            IBinder focusGrantToken, InputChannel outInputChannel) {
         if (hostInputToken == null && !mCanAddInternalSystemWindow) {
             // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
             // embedded windows without providing a host window input token
@@ -814,7 +829,7 @@
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
                     flags, mCanAddInternalSystemWindow ? privateFlags : 0,
-                    mCanAddInternalSystemWindow ? type : 0, outInputChannel);
+                    mCanAddInternalSystemWindow ? type : 0, focusGrantToken, outInputChannel);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -863,4 +878,10 @@
             Binder.restoreCallingIdentity(origId);
         }
     }
+
+    @Override
+    public void setOnBackInvokedCallback(IWindow iWindow,
+            IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException {
+        // TODO: Set the callback to the WindowState of the 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/TaskFpsCallbackController.java b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
new file mode 100644
index 0000000..d9dc9aa
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFpsCallbackController.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.server.wm;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.window.IOnFpsCallbackListener;
+
+import java.util.HashMap;
+
+final class TaskFpsCallbackController {
+
+    private final Context mContext;
+    private final HashMap<IOnFpsCallbackListener, Long> mTaskFpsCallbackListeners;
+    private final HashMap<IOnFpsCallbackListener, IBinder.DeathRecipient> mDeathRecipients;
+
+    TaskFpsCallbackController(Context context) {
+        mContext = context;
+        mTaskFpsCallbackListeners = new HashMap<>();
+        mDeathRecipients = new HashMap<>();
+    }
+
+    void registerCallback(int taskId, IOnFpsCallbackListener listener) {
+        if (mTaskFpsCallbackListeners.containsKey(listener)) {
+            return;
+        }
+
+        final long nativeListener = nativeRegister(listener, taskId);
+        mTaskFpsCallbackListeners.put(listener, nativeListener);
+
+        final IBinder.DeathRecipient deathRecipient = () -> unregisterCallback(listener);
+        try {
+            listener.asBinder().linkToDeath(deathRecipient, 0);
+            mDeathRecipients.put(listener, deathRecipient);
+        } catch (RemoteException e) {
+            // ignore
+        }
+    }
+
+    void unregisterCallback(IOnFpsCallbackListener listener) {
+        if (!mTaskFpsCallbackListeners.containsKey(listener)) {
+            return;
+        }
+
+        listener.asBinder().unlinkToDeath(mDeathRecipients.get(listener), 0);
+        mDeathRecipients.remove(listener);
+
+        nativeUnregister(mTaskFpsCallbackListeners.get(listener));
+        mTaskFpsCallbackListeners.remove(listener);
+    }
+
+    private static native long nativeRegister(IOnFpsCallbackListener listener, int taskId);
+    private static native void nativeUnregister(long ptr);
+}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index b681a96..177d2e6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -24,8 +24,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -39,6 +37,7 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -100,6 +99,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
@@ -254,6 +254,10 @@
     private final Rect mTmpStableBounds = new Rect();
     private final Rect mTmpNonDecorBounds = new Rect();
 
+    //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
+    // implemented
+    HashMap<String, SurfaceControl.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
+
     private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
             new EnsureActivitiesVisibleHelper(this);
     private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
@@ -790,13 +794,8 @@
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
 
-        boolean gotRootSplitScreenFragment = false;
-        boolean gotOpaqueSplitScreenPrimary = false;
-        boolean gotOpaqueSplitScreenSecondary = false;
         boolean gotTranslucentFullscreen = false;
         boolean gotTranslucentAdjacent = false;
-        boolean gotTranslucentSplitScreenPrimary = false;
-        boolean gotTranslucentSplitScreenSecondary = false;
         boolean shouldBeVisible = true;
 
         // This TaskFragment is only considered visible if all its parent TaskFragments are
@@ -815,8 +814,6 @@
         }
 
         final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
-        final int windowingMode = getWindowingMode();
-        final boolean isAssistantType = isActivityTypeAssistant();
         for (int i = parent.getChildCount() - 1; i >= 0; --i) {
             final WindowContainer other = parent.getChildAt(i);
             if (other == null) continue;
@@ -864,37 +861,6 @@
                 }
                 // Multi-window TaskFragment that matches parent bounds would occlude other children
                 return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                    && !gotOpaqueSplitScreenPrimary) {
-                gotRootSplitScreenFragment = true;
-                gotTranslucentSplitScreenPrimary = isTranslucent(other, starting);
-                gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                        && gotOpaqueSplitScreenPrimary) {
-                    // Can't be visible behind another opaque TaskFragment in split-screen-primary.
-                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                }
-            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                    && !gotOpaqueSplitScreenSecondary) {
-                gotRootSplitScreenFragment = true;
-                gotTranslucentSplitScreenSecondary = isTranslucent(other, starting);
-                gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                        && gotOpaqueSplitScreenSecondary) {
-                    // Can't be visible behind another opaque TaskFragment in split-screen-secondary
-                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                }
-            }
-            if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
-                // Can not be visible if we are in split-screen windowing mode and both halves of
-                // the screen are opaque.
-                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-            }
-            if (isAssistantType && gotRootSplitScreenFragment) {
-                // Assistant TaskFragment can't be visible behind split-screen. In addition to
-                // this not making sense, it also works around an issue here we boost the z-order
-                // of the assistant window surfaces in window manager whenever it is visible.
-                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
             }
 
             final TaskFragment otherTaskFrag = other.asTaskFragment();
@@ -920,34 +886,6 @@
             return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
         }
 
-        // Handle cases when there can be a translucent split-screen TaskFragment on top.
-        switch (windowingMode) {
-            case WINDOWING_MODE_FULLSCREEN:
-                if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
-                    // At least one of the split-screen TaskFragment that covers this one is
-                    // translucent.
-                    // When in split mode, home will be reparented to the secondary split while
-                    // leaving TaskFragments not supporting split below. Due to
-                    // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
-                    // the bottom, this makes sure TaskFragments not in split roots won't occlude
-                    // home task unexpectedly.
-                    return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
-                }
-                break;
-            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
-                if (gotTranslucentSplitScreenPrimary) {
-                    // Covered by translucent primary split-screen on top.
-                    return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
-                }
-                break;
-            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
-                if (gotTranslucentSplitScreenSecondary) {
-                    // Covered by translucent secondary split-screen on top.
-                    return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
-                }
-                break;
-        }
-
         // Lastly - check if there is a translucent fullscreen TaskFragment on top.
         return gotTranslucentFullscreen
                 ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
@@ -1683,6 +1621,7 @@
 
     @Override
     void addChild(WindowContainer child, int index) {
+        ActivityRecord r = topRunningActivity();
         mClearedTaskForReuse = false;
 
         boolean isAddingActivity = child.asActivityRecord() != null;
@@ -1697,6 +1636,18 @@
         super.addChild(child, index);
 
         if (isAddingActivity && task != null) {
+
+            // TODO(b/207481538): temporary per-activity screenshoting
+            if (r != null && BackNavigationController.isEnabled()) {
+                ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
+                        r.mActivityComponent.flattenToString());
+                Rect outBounds = r.getBounds();
+                SurfaceControl.ScreenshotHardwareBuffer backBuffer = SurfaceControl.captureLayers(
+                        r.mSurfaceControl,
+                        new Rect(0, 0, outBounds.width(), outBounds.height()),
+                        1f);
+                mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
+            }
             child.asActivityRecord().inHistory = true;
             task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord());
         }
@@ -2290,6 +2241,14 @@
 
     void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
         super.removeChild(child);
+        if (BackNavigationController.isEnabled()) {
+            //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
+            // implemented
+            ActivityRecord r = child.asActivityRecord();
+            if (r != null) {
+                mBackScreenshots.remove(r.mActivityComponent.flattenToString());
+            }
+        }
         if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) {
             removeImmediately("removeLastChild " + child);
         }
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/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1ab191b..b9fa297 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ClipData;
@@ -40,6 +43,7 @@
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
 
+import java.lang.annotation.Retention;
 import java.util.List;
 import java.util.Set;
 
@@ -609,6 +613,7 @@
     /**
      * Checks whether the specified IME client has IME focus or not.
      *
+     * @param windowToken The window token of the input method client
      * @param uid UID of the process to be queried
      * @param pid PID of the process to be queried
      * @param displayId Display ID reported from the client. Note that this method also verifies
@@ -616,7 +621,22 @@
      * @return {@code true} if the IME client specified with {@code uid}, {@code pid}, and
      *         {@code displayId} has IME focus
      */
-    public abstract boolean isInputMethodClientFocus(int uid, int pid, int displayId);
+    public abstract @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken,
+            int uid, int pid, int displayId);
+
+    @Retention(SOURCE)
+    @IntDef({
+            ImeClientFocusResult.HAS_IME_FOCUS,
+            ImeClientFocusResult.NOT_IME_TARGET_WINDOW,
+            ImeClientFocusResult.DISPLAY_ID_MISMATCH,
+            ImeClientFocusResult.INVALID_DISPLAY_ID
+    })
+    public @interface ImeClientFocusResult {
+        int HAS_IME_FOCUS = 0;
+        int NOT_IME_TARGET_WINDOW = -1;
+        int DISPLAY_ID_MISMATCH = -2;
+        int INVALID_DISPLAY_ID = -3;
+    }
 
     /**
      * Checks whether the given {@code uid} is allowed to use the given {@code displayId} or not.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2f0ef4a..6e20fced 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -143,6 +143,7 @@
 import android.Manifest.permission;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -170,14 +171,12 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.Insets;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.configstore.V1_0.OptionalBool;
-import android.hardware.configstore.V1_1.DisplayOrientation;
 import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
-import android.hardware.configstore.V1_1.OptionalDisplayOrientation;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
@@ -229,7 +228,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 import android.view.Display;
-import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IAppTransitionAnimationSpecsFuture;
@@ -282,6 +280,7 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.ClientWindowFrames;
+import android.window.IOnFpsCallbackListener;
 import android.window.TaskSnapshot;
 
 import com.android.internal.R;
@@ -439,8 +438,6 @@
      */
     static final boolean ENABLE_FIXED_ROTATION_TRANSFORM =
             SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true);
-    private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0;
-    private DisplayAddress mPrimaryDisplayPhysicalAddress;
 
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
@@ -589,6 +586,13 @@
     final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
 
     /**
+     * Mapping of displayId to {@link DisplayImePolicy}.
+     * Note that this can be accessed without holding the lock.
+     */
+    volatile Map<Integer, Integer> mDisplayImePolicyCache = Collections.unmodifiableMap(
+            new ArrayMap<>());
+
+    /**
      * Windows whose surface should be destroyed.
      */
     final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
@@ -708,6 +712,7 @@
     final TaskSnapshotController mTaskSnapshotController;
 
     final BlurController mBlurController;
+    final TaskFpsCallbackController mTaskFpsCallbackController;
 
     boolean mIsTouchDevice;
     boolean mIsFakeTouchDevice;
@@ -1354,6 +1359,7 @@
         mStartingSurfaceController = new StartingSurfaceController(this);
 
         mBlurController = new BlurController(mContext, mPowerManager);
+        mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
         mAccessibilityController = new AccessibilityController(this);
     }
 
@@ -1788,8 +1794,7 @@
                 prepareNoneTransitionForRelaunching(activity);
             }
 
-            if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState,
-                    win.isClientLocal())) {
+            if (displayPolicy.areSystemBarsForcedShownLw()) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
             }
 
@@ -1836,6 +1841,7 @@
             displayContent.getInsetsStateController().updateAboveInsetsState(
                     win, false /* notifyInsetsChanged */);
 
+            outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
             getInsetsSourceControls(win, outActiveControls);
         }
 
@@ -2099,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();
@@ -2124,6 +2143,7 @@
                     }
                     w.setDisplayLayoutNeeded();
                     mWindowPlacerLocked.performSurfacePlacement();
+                    w.getDisplayContent().getInputMonitor().updateInputWindowsLw(true);
 
                     // We need to report touchable region changes to accessibility.
                     if (mAccessibilityController.hasCallbacks()) {
@@ -2174,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;
@@ -2196,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);
@@ -2438,23 +2455,6 @@
             configChanged = displayContent.updateOrientation();
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
-            final DisplayInfo displayInfo = win.getDisplayInfo();
-            int transformHint = displayInfo.rotation;
-            // If the window is on the primary display, use the panel orientation to adjust the
-            // transform hint
-            final boolean isPrimaryDisplay = displayInfo.address != null &&
-                    displayInfo.address.equals(mPrimaryDisplayPhysicalAddress);
-            if (isPrimaryDisplay) {
-                transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
-            }
-            outSurfaceControl.setTransformHint(
-                    SurfaceControl.rotationToBufferTransform(transformHint));
-            ProtoLog.v(WM_DEBUG_ORIENTATION,
-                    "Passing transform hint %d for window %s%s",
-                    transformHint, win,
-                    isPrimaryDisplay ? " on primary display with orientation "
-                            + mPrimaryDisplayOrientation : "");
-
             if (toBeDisplayed && win.mIsWallpaper) {
                 displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
             }
@@ -2508,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);
         }
 
@@ -2591,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);
         }
@@ -3838,6 +3833,40 @@
     }
 
     /**
+     * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. The returned
+     * bitmap will be full size and will not include any secure content.
+     *
+     * @param taskId The task ID of the task for which a snapshot is requested.
+     * @return The Bitmap, or null if no task with the specified ID can be found or the bitmap could
+     * not be generated.
+     */
+    @Nullable public Bitmap captureTaskBitmap(int taskId) {
+        if (mTaskSnapshotController.shouldDisableSnapshots()) {
+            return null;
+        }
+
+        synchronized (mGlobalLock) {
+            final Task task = mRoot.anyTaskForId(taskId);
+            if (task == null) {
+                return null;
+            }
+
+            task.getBounds(mTmpRect);
+            final SurfaceControl sc = task.getSurfaceControl();
+            final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers(
+                    new SurfaceControl.LayerCaptureArgs.Builder(sc)
+                            .setSourceCrop(mTmpRect)
+                            .build());
+            if (buffer == null) {
+                Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId);
+                return null;
+            }
+
+            return buffer.asBitmap();
+        }
+    }
+
+    /**
      * In case a task write/delete operation was lost because the system crashed, this makes sure to
      * clean up the directory to remove obsolete files.
      *
@@ -4316,6 +4345,15 @@
         }
     }
 
+    void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) {
+        synchronized (mGlobalLock) {
+            final WindowState win = windowForClientLocked(session, window, true);
+            if (win.setKeepClearAreas(keepClearAreas)) {
+                win.getDisplayContent().updateKeepClearAreas();
+            }
+        }
+    }
+
     @Override
     public void registerDisplayFoldListener(IDisplayFoldListener listener) {
         mPolicy.registerDisplayFoldListener(listener);
@@ -4888,9 +4926,6 @@
         mTaskSnapshotController.systemReady();
         mHasWideColorGamutSupport = queryWideColorGamutSupport();
         mHasHdrSupport = queryHdrSupport();
-        mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation();
-        mPrimaryDisplayPhysicalAddress =
-            DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId());
         UiThread.getHandler().post(mSettingsObserver::loadSettings);
         IVrManager vrManager = IVrManager.Stub.asInterface(
                 ServiceManager.getService(Context.VR_SERVICE));
@@ -4953,39 +4988,6 @@
         return false;
     }
 
-    private static @Surface.Rotation int queryPrimaryDisplayOrientation() {
-        Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop =
-                SurfaceFlingerProperties.primary_display_orientation();
-        if (prop.isPresent()) {
-            switch (prop.get()) {
-                case ORIENTATION_90: return Surface.ROTATION_90;
-                case ORIENTATION_180: return Surface.ROTATION_180;
-                case ORIENTATION_270: return Surface.ROTATION_270;
-                case ORIENTATION_0:
-                default:
-                    return Surface.ROTATION_0;
-            }
-        }
-        try {
-            ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService();
-            OptionalDisplayOrientation primaryDisplayOrientation =
-                    surfaceFlinger.primaryDisplayOrientation();
-            if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) {
-                switch (primaryDisplayOrientation.value) {
-                    case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90;
-                    case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180;
-                    case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270;
-                    case DisplayOrientation.ORIENTATION_0:
-                    default:
-                        return Surface.ROTATION_0;
-                }
-            }
-        } catch (Exception e) {
-            // Use default value if we can't talk to config store.
-        }
-        return Surface.ROTATION_0;
-    }
-
     // Returns an input target which is mapped to the given input token. This can be a WindowState
     // or an embedded window.
     @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) {
@@ -5684,6 +5686,11 @@
     }
 
     @Override
+    public void saveWindowTraceToFile() {
+        mWindowTracing.saveForBugreport(null /* printwriter */);
+    }
+
+    @Override
     public boolean isWindowTraceEnabled() {
         return mWindowTracing.isEnabled();
     }
@@ -6419,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);
@@ -6437,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());
@@ -6908,6 +6916,7 @@
     void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
         synchronized (mGlobalLock) {
             mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
+            mRoot.updateDisplayImePolicyCache();
         }
     }
 
@@ -6963,23 +6972,6 @@
         }
     }
 
-    @Override
-    public void setForwardedInsets(int displayId, Insets insets) throws RemoteException {
-        synchronized (mGlobalLock) {
-            final DisplayContent dc = mRoot.getDisplayContent(displayId);
-            if (dc == null) {
-                return;
-            }
-            final int callingUid = Binder.getCallingUid();
-            final int displayOwnerUid = dc.getDisplay().getOwnerUid();
-            if (callingUid != displayOwnerUid) {
-                throw new SecurityException(
-                        "Only owner of the display can set ForwardedInsets to it.");
-            }
-            dc.setForwardedInsets(insets);
-        }
-    }
-
     MousePositionTracker mMousePositionTracker = new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
@@ -7066,6 +7058,13 @@
         }
     }
 
+    PointF getLatestMousePosition() {
+        synchronized (mMousePositionTracker) {
+            return new PointF(mMousePositionTracker.mLatestMouseX,
+                    mMousePositionTracker.mLatestMouseY);
+        }
+    }
+
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
@@ -7332,16 +7331,14 @@
         if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "getDisplayImePolicy()")) {
             throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
         }
-        final DisplayContent dc = mRoot.getDisplayContent(displayId);
-        if (dc == null) {
+        final Map<Integer, Integer> displayImePolicyCache = mDisplayImePolicyCache;
+        if (!displayImePolicyCache.containsKey(displayId)) {
             ProtoLog.w(WM_ERROR,
                     "Attempted to get IME policy of a display that does not exist: %d",
                     displayId);
             return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
         }
-        synchronized (mGlobalLock) {
-            return dc.getImePolicy();
-        }
+        return displayImePolicyCache.get(displayId);
     }
 
     @Override
@@ -7690,19 +7687,32 @@
         }
 
         @Override
-        public boolean isInputMethodClientFocus(int uid, int pid, int displayId) {
+        public @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken,
+                int uid, int pid, int displayId) {
             if (displayId == Display.INVALID_DISPLAY) {
-                return false;
+                return ImeClientFocusResult.INVALID_DISPLAY_ID;
             }
             synchronized (mGlobalLock) {
                 final DisplayContent displayContent = mRoot.getTopFocusedDisplayContent();
+                final WindowState window = mWindowMap.get(windowToken);
+                if (window == null) {
+                    return ImeClientFocusResult.NOT_IME_TARGET_WINDOW;
+                }
+                final int tokenDisplayId = window.getDisplayContent().getDisplayId();
+                if (tokenDisplayId != displayId) {
+                    Slog.e(TAG, "isInputMethodClientFocus: display ID mismatch."
+                            + " from client: " + displayId
+                            + " from window: " + tokenDisplayId);
+                    return ImeClientFocusResult.DISPLAY_ID_MISMATCH;
+                }
                 if (displayContent == null
                         || displayContent.getDisplayId() != displayId
                         || !displayContent.hasAccess(uid)) {
-                    return false;
+                    return ImeClientFocusResult.INVALID_DISPLAY_ID;
                 }
+
                 if (displayContent.isInputMethodClientFocus(uid, pid)) {
-                    return true;
+                    return ImeClientFocusResult.HAS_IME_FOCUS;
                 }
                 // Okay, how about this...  what is the current focus?
                 // It seems in some cases we may not have moved the IM
@@ -7715,10 +7725,11 @@
                 final WindowState currentFocus = displayContent.mCurrentFocus;
                 if (currentFocus != null && currentFocus.mSession.mUid == uid
                         && currentFocus.mSession.mPid == pid) {
-                    return currentFocus.canBeImeTarget();
+                    return currentFocus.canBeImeTarget() ? ImeClientFocusResult.HAS_IME_FOCUS
+                            : ImeClientFocusResult.NOT_IME_TARGET_WINDOW;
                 }
             }
-            return false;
+            return ImeClientFocusResult.NOT_IME_TARGET_WINDOW;
         }
 
         @Override
@@ -7817,9 +7828,7 @@
 
         @Override
         public @DisplayImePolicy int getDisplayImePolicy(int displayId) {
-            synchronized (mGlobalLock) {
-                return WindowManagerService.this.getDisplayImePolicy(displayId);
-            }
+            return WindowManagerService.this.getDisplayImePolicy(displayId);
         }
 
         @Override
@@ -8263,7 +8272,8 @@
      */
     void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
                            SurfaceControl surface, IWindow window, IBinder hostInputToken,
-                           int flags, int privateFlags, int type, InputChannel outInputChannel) {
+                           int flags, int privateFlags, int type, IBinder focusGrantToken,
+                           InputChannel outInputChannel) {
         final InputApplicationHandle applicationHandle;
         final String name;
         final InputChannel clientChannel;
@@ -8271,7 +8281,7 @@
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, window,
                             mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type,
-                            displayId);
+                            displayId, focusGrantToken);
             clientChannel = win.openInputChannel();
             mEmbeddedWindowController.add(clientChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
@@ -8475,6 +8485,7 @@
     public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
             InsetsState outInsetsState) {
         final boolean fromLocal = Binder.getCallingPid() == myPid();
+        final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -8483,9 +8494,20 @@
                     throw new WindowManager.InvalidDisplayException("Display#" + displayId
                             + "could not be found!");
                 }
-                final WindowToken windowToken = dc.getWindowToken(attrs.token);
-                return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken, outInsetsState,
-                        fromLocal);
+                final WindowToken token = dc.getWindowToken(attrs.token);
+                final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
+                        attrs.packageName, uid);
+                final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
+                final boolean hasCompatScale =
+                        WindowState.hasCompatScale(attrs, token, overrideScale);
+                outInsetsState.set(state, hasCompatScale || fromLocal);
+                if (hasCompatScale) {
+                    final float compatScale = token != null && token.hasSizeCompatBounds()
+                            ? token.getSizeCompatScale() * overrideScale
+                            : overrideScale;
+                    outInsetsState.scale(1f / compatScale);
+                }
+                return dc.getDisplayPolicy().areSystemBarsForcedShownLw();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -8538,10 +8560,10 @@
         }
     }
 
-    void grantEmbeddedWindowFocus(Session session, IBinder inputToken, boolean grantFocus) {
+    void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
         synchronized (mGlobalLock) {
             final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
-                    mEmbeddedWindowController.get(inputToken);
+                    mEmbeddedWindowController.getByFocusToken(focusToken);
             if (embeddedWindow == null) {
                 Slog.e(TAG, "Embedded window not found");
                 return;
@@ -8550,6 +8572,11 @@
                 Slog.e(TAG, "Window not in session:" + session);
                 return;
             }
+            IBinder inputToken = embeddedWindow.getInputChannelToken();
+            if (inputToken == null) {
+                Slog.e(TAG, "Focus token found but input channel token not found");
+                return;
+            }
             SurfaceControl.Transaction t = mTransactionFactory.get();
             final int displayId = embeddedWindow.mDisplayId;
             if (grantFocus) {
@@ -8714,20 +8741,21 @@
     }
 
     boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
+        final Task imeTargetWindowTask;
         synchronized (mGlobalLock) {
             final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken);
             if (imeTargetWindow == null) {
                 return false;
             }
-            final Task imeTargetWindowTask = imeTargetWindow.getTask();
+            imeTargetWindowTask = imeTargetWindow.getTask();
             if (imeTargetWindowTask == null) {
                 return false;
             }
-            final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
-                    imeTargetWindowTask.mUserId, false /* isLowResolution */,
-                    false /* restoreFromDisk */);
-            return snapshot != null && snapshot.hasImeSurface();
         }
+        final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+                imeTargetWindowTask.mUserId, false /* isLowResolution */,
+                false /* restoreFromDisk */);
+        return snapshot != null && snapshot.hasImeSurface();
     }
 
     @Override
@@ -8772,4 +8800,34 @@
         mTaskTransitionSpec = null;
     }
 
+    @Override
+    @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)
+    public void registerTaskFpsCallback(@IntRange(from = 0) int taskId,
+            IOnFpsCallbackListener listener) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)
+                != PackageManager.PERMISSION_GRANTED) {
+            final int pid = Binder.getCallingPid();
+            throw new SecurityException("Access denied to process: " + pid
+                    + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
+        }
+
+        if (mRoot.anyTaskForId(taskId) == null) {
+            throw new IllegalArgumentException("no task with taskId: " + taskId);
+        }
+
+        mTaskFpsCallbackController.registerCallback(taskId, listener);
+    }
+
+    @Override
+    @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER)
+    public void unregisterTaskFpsCallback(IOnFpsCallbackListener listener) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER)
+                != PackageManager.PERMISSION_GRANTED) {
+            final int pid = Binder.getCallingPid();
+            throw new SecurityException("Access denied to process: " + pid
+                    + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
+        }
+
+        mTaskFpsCallbackController.unregisterCallback(listener);
+    }
 }
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 e4d3e05..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;
@@ -172,6 +171,7 @@
 import static com.android.server.wm.WindowStateProto.IS_ON_SCREEN;
 import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY;
 import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
+import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS;
 import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.REMOVED;
 import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
@@ -196,6 +196,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.gui.TouchOcclusionMode;
 import android.os.Binder;
@@ -378,7 +379,6 @@
      */
     final boolean mForceSeamlesslyRotate;
     SeamlessRotator mPendingSeamlessRotate;
-    long mFinishSeamlessRotateFrameNumber;
 
     private RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
 
@@ -442,9 +442,8 @@
 
     // Current transformation being applied.
     float mGlobalScale=1;
-    float mLastGlobalScale=1;
     float mInvGlobalScale=1;
-    float mOverrideScale = 1;
+    final float mOverrideScale;
     float mHScale=1, mVScale=1;
     float mLastHScale=1, mLastVScale=1;
 
@@ -471,6 +470,12 @@
      * Coordinates are relative to the window's position.
      */
     private final List<Rect> mExclusionRects = new ArrayList<>();
+    /**
+     * List of rects which should ideally not be covered by floating windows like Pip.
+     *
+     * Coordinates are relative to the window's position.
+     */
+    private final List<Rect> mKeepClearAreas = new ArrayList<>();
 
     // 0 = left, 1 = right
     private final int[] mLastRequestedExclusionHeight = {0, 0};
@@ -647,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;
 
@@ -699,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();
 
     /**
@@ -962,7 +963,6 @@
         }
 
         mPendingSeamlessRotate.finish(t, this);
-        mFinishSeamlessRotateFrameNumber = getFrameNumber();
         mPendingSeamlessRotate = null;
 
         getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -1012,6 +1012,55 @@
         }
     }
 
+    /**
+     * @return a list of rects that should ideally not be covered by floating windows like pip.
+     *         The returned rect coordinates are relative to the display origin.
+     */
+    List<Rect> getKeepClearAreas() {
+        final Matrix tmpMatrix = new Matrix();
+        final float[] tmpFloat9 = new float[9];
+        return getKeepClearAreas(tmpMatrix, tmpFloat9);
+    }
+
+    /**
+     * @param tmpMatrix a temporary matrix to be used for transformations
+     * @param float9 a temporary array of 9 floats
+     *
+     * @return a list of rects that should ideally not be covered by floating windows like pip.
+     *         The returned rect coordinates are relative to the display origin.
+     */
+    List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) {
+        getTransformationMatrix(float9, tmpMatrix);
+
+        // Translate all keep-clear rects to screen coordinates.
+        final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>();
+        final RectF tmpRect = new RectF();
+        Rect curr;
+        for (Rect r : mKeepClearAreas) {
+            tmpRect.set(r);
+            tmpMatrix.mapRect(tmpRect);
+            curr = new Rect();
+            tmpRect.roundOut(curr);
+            transformedKeepClearAreas.add(curr);
+        }
+        return transformedKeepClearAreas;
+    }
+
+    /**
+     * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined
+     *                       in window coordinate space
+     *
+     * @return true if there is a change in the list of keep-clear areas; false otherwise
+     */
+    boolean setKeepClearAreas(List<Rect> keepClearAreas) {
+        if (mKeepClearAreas.equals(keepClearAreas)) {
+            return false;
+        }
+        mKeepClearAreas.clear();
+        mKeepClearAreas.addAll(keepClearAreas);
+        return true;
+    }
+
     interface PowerManagerWrapper {
         void wakeUp(long time, @WakeReason int reason, String details);
 
@@ -1091,6 +1140,7 @@
             mSubLayer = 0;
             mWinAnimator = null;
             mWpcForDisplayAreaConfigChanges = null;
+            mOverrideScale = 1f;
             return;
         }
         mDeathRecipient = deathRecipient;
@@ -1138,6 +1188,7 @@
         mLayer = 0;
         mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
                 mAttrs.packageName, s.mUid);
+        updateGlobalScale();
 
         // Make sure we initial all fields before adding to parentWindow, to prevent exception
         // during onDisplayChanged.
@@ -1167,6 +1218,23 @@
         mSession.windowAddedLocked();
     }
 
+    boolean updateGlobalScale() {
+        if (hasCompatScale()) {
+            if (mOverrideScale != 1f) {
+                mGlobalScale = mToken.hasSizeCompatBounds()
+                        ? mToken.getSizeCompatScale() * mOverrideScale
+                        : mOverrideScale;
+            } else {
+                mGlobalScale = mToken.getSizeCompatScale();
+            }
+            mInvGlobalScale = 1f / mGlobalScale;
+            return true;
+        }
+
+        mGlobalScale = mInvGlobalScale = 1f;
+        return false;
+    }
+
     /**
      * @return {@code true} if the application runs in size compatibility mode or has an app level
      * scaling override set.
@@ -1175,7 +1243,7 @@
      * @see ActivityRecord#hasSizeCompatBounds()
      */
     boolean hasCompatScale() {
-        return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord);
+        return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale);
     }
 
     /**
@@ -1183,11 +1251,16 @@
      * @see android.content.res.CompatibilityInfo#supportsScreen
      * @see ActivityRecord#hasSizeCompatBounds()
      */
-    static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken windowToken) {
-        return (attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0
-                || (windowToken != null && windowToken.hasSizeCompatBounds()
-                // Exclude starting window because it is not displayed by the application.
-                && attrs.type != TYPE_APPLICATION_STARTING);
+    static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken token,
+            float overrideScale) {
+        if ((attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
+            return true;
+        }
+        if (attrs.type == TYPE_APPLICATION_STARTING) {
+            // Exclude starting window because it is not displayed by the application.
+            return false;
+        }
+        return token != null && token.hasSizeCompatBounds() || overrideScale != 1f;
     }
 
     /**
@@ -1338,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() {
@@ -1483,7 +1556,7 @@
             }
         } else {
             // The orientation change is completed. If it was hidden by the animation, reshow it.
-            mDisplayContent.finishFadeRotationAnimation(mToken);
+            mDisplayContent.finishAsyncRotation(mToken);
         }
     }
 
@@ -1528,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.
@@ -1691,21 +1773,6 @@
                 && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
     }
 
-    void prelayout() {
-        if (hasCompatScale()) {
-            if (mOverrideScale != 1f) {
-                mGlobalScale = mToken.hasSizeCompatBounds()
-                        ? mToken.getSizeCompatScale() * mOverrideScale
-                        : mOverrideScale;
-            } else {
-                mGlobalScale = mToken.getSizeCompatScale();
-            }
-            mInvGlobalScale = 1 / mGlobalScale;
-        } else {
-            mGlobalScale = mInvGlobalScale = 1;
-        }
-    }
-
     @Override
     boolean hasContentToDisplay() {
         if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
@@ -2708,6 +2775,7 @@
                 region.set(-dw, -dh, dw + dw, dh + dh);
             }
             subtractTouchExcludeRegionIfNeeded(region);
+
         } else {
             // Not modal
             getTouchableRegion(region);
@@ -2718,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-
@@ -2928,7 +3004,6 @@
         @Override
         public void binderDied() {
             try {
-                boolean resetSplitScreenResizing = false;
                 synchronized (mWmService.mGlobalLock) {
                     final WindowState win = mWmService
                             .windowForClientLocked(mSession, mClient, false);
@@ -2944,16 +3019,6 @@
                         WindowState.this.removeIfPossible();
                     }
                 }
-                if (resetSplitScreenResizing) {
-                    try {
-                        // Note: this calls into ActivityManager, so we must *not* hold the window
-                        // manager lock while calling this.
-                        mWmService.mActivityTaskManager.setSplitScreenResizing(false);
-                    } catch (RemoteException e) {
-                        // Local call, shouldn't return RemoteException.
-                        throw e.rethrowAsRuntimeException();
-                    }
-                }
             } catch (IllegalArgumentException ex) {
                 // This will happen if the window has already been removed.
             }
@@ -4059,10 +4124,12 @@
         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);
+        for (Rect r : getKeepClearAreas()) {
+            r.dumpDebug(proto, KEEP_CLEAR_AREAS);
+        }
         proto.end(token);
     }
 
@@ -4198,7 +4265,6 @@
         } else {
             pw.print("null");
         }
-        pw.println(" finishedFrameNumber=" + mFinishSeamlessRotateFrameNumber);
 
         if (mHScale != 1 || mVScale != 1) {
             pw.println(prefix + "mHScale=" + mHScale
@@ -4231,6 +4297,7 @@
         }
         pw.println(prefix + "isOnScreen=" + isOnScreen());
         pw.println(prefix + "isVisible=" + isVisible());
+        pw.println(prefix + "keepClearAreas=" + getKeepClearAreas());
         if (dumpAll) {
             final String visibilityString = mRequestedVisibilities.toString();
             if (!visibilityString.isEmpty()) {
@@ -5259,7 +5326,6 @@
             mLastVScale != newVScale ) {
             getPendingTransaction().setMatrix(getSurfaceControl(),
                 newHScale, 0, 0, newVScale);
-            mLastGlobalScale = mGlobalScale;
             mLastHScale = newHScale;
             mLastVScale = newVScale;
         }
@@ -5567,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();
     }
@@ -5763,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.
@@ -5847,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
@@ -6005,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/Android.bp b/services/core/jni/Android.bp
index b643883..79a980f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -68,6 +68,7 @@
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
         "com_android_server_sensor_SensorService.cpp",
+        "com_android_server_wm_TaskFpsCallbackController.cpp",
         "onload.cpp",
         ":lib_cachedAppOptimizer_native",
         ":lib_networkStatsFactory_native",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3cd4e5e..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);
@@ -286,6 +287,7 @@
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setMotionClassifierEnabled(bool enabled);
+    void notifyPointerDisplayIdChanged();
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -362,6 +364,9 @@
         // Pointer speed.
         int32_t pointerSpeed;
 
+        // Pointer acceleration.
+        float pointerAcceleration;
+
         // True if pointer gestures are enabled.
         bool pointerGesturesEnabled;
 
@@ -411,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;
@@ -438,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));
@@ -627,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;
@@ -1065,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);
@@ -1494,6 +1518,18 @@
     mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
 }
 
+void NativeInputManager::notifyPointerDisplayIdChanged() {
+    int32_t pointerDisplayId = getPointerDisplayId();
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+        mLocked.pointerDisplayId = pointerDisplayId;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 // ----------------------------------------------------------------------------
 
 static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
@@ -1573,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) {
@@ -1862,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);
@@ -2186,6 +2236,18 @@
             InputReaderConfiguration::CHANGE_DISPLAY_INFO);
 }
 
+static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->notifyPointerDisplayIdChanged();
+}
+
+static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+                                                         jint displayId, jboolean isEligible) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
+                                                                                  isEligible);
+}
+
 static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
     im->getInputManager()->getReader().requestRefreshConfiguration(
@@ -2312,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;",
@@ -2340,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},
@@ -2370,6 +2434,9 @@
         {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay},
         {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged},
         {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation},
+        {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged},
+        {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V",
+         (void*)nativeSetDisplayEligibilityForPointerCapture},
         {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled},
         {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;",
          (void*)nativeGetSensorList},
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/jni/com_android_server_wm_TaskFpsCallbackController.cpp b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp
new file mode 100644
index 0000000..0202306
--- /dev/null
+++ b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TaskFpsCallbackController"
+
+#include <android/gui/BnFpsListener.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+namespace {
+
+struct {
+    jclass mClass;
+    jmethodID mDispatchOnFpsReported;
+} gCallbackClassInfo;
+
+struct TaskFpsCallback : public gui::BnFpsListener {
+    TaskFpsCallback(JNIEnv* env, jobject listener) : mListener(env->NewWeakGlobalRef(listener)) {}
+
+    binder::Status onFpsReported(float fps) override {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onFpsReported.");
+
+        jobject listener = env->NewGlobalRef(mListener);
+        if (listener == NULL) {
+            // Weak reference went out of scope
+            return binder::Status::ok();
+        }
+        env->CallStaticVoidMethod(gCallbackClassInfo.mClass,
+                                  gCallbackClassInfo.mDispatchOnFpsReported, listener,
+                                  static_cast<jfloat>(fps));
+        env->DeleteGlobalRef(listener);
+
+        if (env->ExceptionCheck()) {
+            ALOGE("TaskFpsCallback.onFpsReported() failed.");
+            LOGE_EX(env);
+            env->ExceptionClear();
+        }
+        return binder::Status::ok();
+    }
+
+protected:
+    virtual ~TaskFpsCallback() {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->DeleteWeakGlobalRef(mListener);
+    }
+
+private:
+    jweak mListener;
+};
+
+jlong nativeRegister(JNIEnv* env, jclass clazz, jobject obj, jint taskId) {
+    TaskFpsCallback* callback = new TaskFpsCallback(env, obj);
+
+    if (SurfaceComposerClient::addFpsListener(taskId, callback) != OK) {
+        constexpr auto error_msg = "Couldn't addFpsListener";
+        ALOGE(error_msg);
+        jniThrowRuntimeException(env, error_msg);
+    }
+    callback->incStrong((void*)nativeRegister);
+
+    return reinterpret_cast<jlong>(callback);
+}
+
+void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) {
+    sp<TaskFpsCallback> callback = reinterpret_cast<TaskFpsCallback*>(ptr);
+
+    if (SurfaceComposerClient::removeFpsListener(callback) != OK) {
+        constexpr auto error_msg = "Couldn't removeFpsListener";
+        ALOGE(error_msg);
+        jniThrowRuntimeException(env, error_msg);
+    }
+
+    callback->decStrong((void*)nativeRegister);
+}
+
+static const JNINativeMethod gMethods[] = {
+        /* name, signature, funcPtr */
+        {"nativeRegister", "(Landroid/window/IOnFpsCallbackListener;I)J", (void*)nativeRegister},
+        {"nativeUnregister", "(J)V", (void*)nativeUnregister}};
+
+} // namespace
+
+int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/wm/TaskFpsCallbackController",
+                                       gMethods, NELEM(gMethods));
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    jclass clazz = env->FindClass("android/window/TaskFpsCallback");
+    gCallbackClassInfo.mClass = MakeGlobalRefOrDie(env, clazz);
+    gCallbackClassInfo.mDispatchOnFpsReported =
+            env->GetStaticMethodID(clazz, "dispatchOnFpsReported",
+                                   "(Landroid/window/IOnFpsCallbackListener;F)V");
+    return 0;
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 80d7055..ba5b3f5 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
+int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 };
 
 using namespace android;
@@ -123,5 +124,6 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_app_GameManagerService(env);
+    register_com_android_server_wm_TaskFpsCallbackController(env);
     return JNI_VERSION_1_4;
 }
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 df9ab50..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;
@@ -145,6 +146,10 @@
     private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_ENABLED =
             "preferential-network-service-enabled";
     private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
+    private static final String TAG_WIFI_MIN_SECURITY = "wifi-min-security";
+    private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist";
+    private static final String TAG_SSID_DENYLIST = "ssid-denylist";
+    private static final String TAG_SSID = "ssid";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -237,6 +242,14 @@
     // List of package names to keep cached.
     List<String> keepUninstalledPackages;
 
+    // The allowlist of SSIDs the device may connect to.
+    // By default, the allowlist restriction is deactivated.
+    List<String> mSsidAllowlist;
+
+    // The denylist of SSIDs the device may not connect to.
+    // By default, the denylist restriction is deactivated.
+    List<String> mSsidDenylist;
+
     // TODO: review implementation decisions with frameworks team
     boolean specifiesGlobalProxy = false;
     String globalProxySpec = null;
@@ -294,10 +307,14 @@
     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;
 
+    int mWifiMinimumSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN;
+
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
         this.info = info;
         this.isParent = isParent;
@@ -574,6 +591,15 @@
         if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) {
             writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled);
         }
+        if (mWifiMinimumSecurityLevel != DevicePolicyManager.WIFI_SECURITY_OPEN) {
+            writeAttributeValueToXml(out, TAG_WIFI_MIN_SECURITY, mWifiMinimumSecurityLevel);
+        }
+        if (mSsidAllowlist != null && !mSsidAllowlist.isEmpty()) {
+            writeAttributeValuesToXml(out, TAG_SSID_ALLOWLIST, TAG_SSID, mSsidAllowlist);
+        }
+        if (mSsidDenylist != null && !mSsidDenylist.isEmpty()) {
+            writeAttributeValuesToXml(out, TAG_SSID_DENYLIST, TAG_SSID, mSsidDenylist);
+        }
     }
 
     void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -826,6 +852,14 @@
             } else if (TAG_USB_DATA_SIGNALING.equals(tag)) {
                 mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
                         USB_DATA_SIGNALING_ENABLED_DEFAULT);
+            } else if (TAG_WIFI_MIN_SECURITY.equals(tag)) {
+                mWifiMinimumSecurityLevel = parser.getAttributeInt(null, ATTR_VALUE);
+            } else if (TAG_SSID_ALLOWLIST.equals(tag)) {
+                mSsidAllowlist = new ArrayList<>();
+                readAttributeValues(parser, TAG_SSID, mSsidAllowlist);
+            } else if (TAG_SSID_DENYLIST.equals(tag)) {
+                mSsidDenylist = new ArrayList<>();
+                readAttributeValues(parser, TAG_SSID, mSsidDenylist);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1184,5 +1218,14 @@
 
         pw.print("mUsbDataSignaling=");
         pw.println(mUsbDataSignalingEnabled);
+
+        pw.print("mWifiMinimumSecurityLevel=");
+        pw.println(mWifiMinimumSecurityLevel);
+
+        pw.print("mSsidAllowlist=");
+        pw.println(mSsidAllowlist);
+
+        pw.print("mSsidDenylist=");
+        pw.println(mSsidDenylist);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index ebe9f93..200b120 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -19,6 +19,7 @@
 import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyDrawableResource;
 import android.app.admin.DevicePolicySafetyChecker;
+import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.ManagedProfileProvisioningParams;
@@ -170,11 +171,22 @@
     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;
+    }
+
+    @Override
+    public void setStrings(@NonNull List<DevicePolicyStringResource> strings){}
+
+    @Override
+    public void resetStrings(String[] stringIds){}
+
+    @Override
+    public ParcelableResource getString(String stringId) {
         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 53422940..e70c071 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java
@@ -16,17 +16,19 @@
 
 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;
 
 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;
 import android.util.AtomicFile;
@@ -64,14 +66,26 @@
     private static final String ATTR_DRAWABLE_STYLE = "drawable-style";
     private static final String ATTR_DRAWABLE_SOURCE = "drawable-source";
     private static final String ATTR_DRAWABLE_ID = "drawable-id";
+    private static final String TAG_STRING_ENTRY = "string-entry";
+    private static final String ATTR_SOURCE_ID = "source-id";
 
-
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    /**
+     * Map of <drawable_id, <style_id, resource_value>>
+     */
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForStyle = new HashMap<>();
 
-    private final Map<Integer, Map<Integer, ParcelableResource>>
+    /**
+     * Map of <drawable_id, <source_id, resource_value>>
+     */
+    private final Map<String, Map<String, ParcelableResource>>
             mUpdatedDrawablesForSource = new HashMap<>();
 
+    /**
+     * Map of <string_id, resource_value>
+     */
+    private final Map<String, ParcelableResource> mUpdatedStrings = new HashMap<>();
+
     private final Object mLock = new Object();
     private final Injector mInjector;
 
@@ -89,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);
@@ -112,14 +129,12 @@
     }
 
     private boolean updateDrawable(
-            int drawableId, int drawableStyle, ParcelableResource updatableResource) {
+            String drawableId, String drawableStyle, ParcelableResource updatableResource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
-            throw new IllegalArgumentException(
-                    "Can't update drawable resource, invalid drawable " + "id " + drawableId);
+            Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
         if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) {
-            throw new IllegalArgumentException(
-                    "Can't update drawable resource, invalid style id " + drawableStyle);
+            Log.w(TAG, "Updating a resource for an unknown style id " + drawableStyle);
         }
         synchronized (mLock) {
             if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) {
@@ -137,14 +152,12 @@
 
     // 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)) {
-            throw new IllegalArgumentException("Can't update drawable resource, invalid drawable "
-                    + "id " + drawableId);
+            Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId);
         }
         if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) {
-            throw new IllegalArgumentException("Can't update drawable resource, invalid source id "
-                    + drawableSource);
+            Log.w(TAG, "Updating a resource for an unknown source id " + drawableSource);
         }
         synchronized (mLock) {
             if (!mUpdatedDrawablesForSource.containsKey(drawableId)) {
@@ -163,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;
             }
@@ -181,21 +194,17 @@
 
     @Nullable
     ParcelableResource getDrawable(
-            int drawableId, int drawableStyle, int drawableSource) {
+            String drawableId, String drawableStyle, String drawableSource) {
         if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) {
-            Log.e(TAG, "Can't get updated drawable resource, invalid drawable id "
-                    + drawableId);
-            return null;
+            Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId);
         }
         if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) {
-            Log.e(TAG, "Can't get updated drawable resource, invalid style id "
+            Log.w(TAG, "Getting an updated resource for an unknown drawable style "
                     + drawableStyle);
-            return null;
         }
         if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) {
-            Log.e(TAG, "Can't get updated drawable resource, invalid source id "
+            Log.w(TAG, "Getting an updated resource for an unknown drawable Source "
                     + drawableSource);
-            return null;
         }
         if (mUpdatedDrawablesForSource.containsKey(drawableId)
                 && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) {
@@ -216,6 +225,75 @@
         return null;
     }
 
+    /**
+     * Returns {@code false} if no resources were updated.
+     */
+    boolean updateStrings(@NonNull List<DevicePolicyStringResource> strings) {
+        boolean updated = false;
+        for (int i = 0; i < strings.size(); i++) {
+            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) {
+            return false;
+        }
+        synchronized (mLock) {
+            write();
+            return true;
+        }
+    }
+
+    private boolean updateString(String stringId, ParcelableResource updatableResource) {
+        if (!UPDATABLE_STRING_IDS.contains(stringId)) {
+            Log.w(TAG, "Updating a resource for an unknown string id " + stringId);
+        }
+        synchronized (mLock) {
+            ParcelableResource current = mUpdatedStrings.get(stringId);
+            if (updatableResource.equals(current)) {
+                return false;
+            }
+            mUpdatedStrings.put(stringId, updatableResource);
+            return true;
+        }
+    }
+
+    /**
+     * Returns {@code false} if no resources were removed.
+     */
+    boolean removeStrings(@NonNull String[] stringIds) {
+        synchronized (mLock) {
+            boolean removed = false;
+            for (int i = 0; i < stringIds.length; i++) {
+                String stringId = stringIds[i];
+                removed |= mUpdatedStrings.remove(stringId) != null;
+            }
+            if (!removed) {
+                return false;
+            }
+            write();
+            return true;
+        }
+    }
+
+    @Nullable
+    ParcelableResource getString(String stringId) {
+        if (!UPDATABLE_STRING_IDS.contains(stringId)) {
+            Log.w(TAG, "Getting an updated resource for an unknown string id " + stringId);
+        }
+
+        if (mUpdatedStrings.containsKey(stringId)) {
+            return mUpdatedStrings.get(stringId);
+        }
+
+        Log.d(TAG, "No updated string found for string id " + stringId);
+        return null;
+    }
+
     private void write() {
         Log.d(TAG, "Writing updated resources to file.");
         new ResourcesReaderWriter().writeToFileLocked();
@@ -319,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());
@@ -341,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());
@@ -362,6 +440,18 @@
                     out.endTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY);
                 }
             }
+            if (mUpdatedStrings != null && !mUpdatedStrings.isEmpty()) {
+                for (Map.Entry<String, ParcelableResource> entry
+                        : mUpdatedStrings.entrySet()) {
+                    out.startTag(/* namespace= */ null, TAG_STRING_ENTRY);
+                    out.attribute(
+                            /* namespace= */ null,
+                            ATTR_SOURCE_ID,
+                            entry.getKey());
+                    entry.getValue().writeToXmlFile(out);
+                    out.endTag(/* namespace= */ null, TAG_STRING_ENTRY);
+                }
+            }
         }
 
         private boolean readInner(
@@ -372,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,
@@ -380,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,
@@ -388,19 +478,25 @@
                     }
                     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,
                                 ParcelableResource.createFromXml(parser));
                     }
                     break;
+                case TAG_STRING_ENTRY:
+                    String sourceId = parser.getAttributeValue(
+                            /* namespace= */ null, ATTR_SOURCE_ID);
+                    mUpdatedStrings.put(
+                            sourceId, ParcelableResource.createFromXml(parser));
+                    break;
                 default:
                     Log.e(TAG, "Unexpected tag: " + tag);
                     return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0f15db1..e0b6273 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -59,6 +59,7 @@
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE;
+import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_STRING;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION;
@@ -99,6 +100,18 @@
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
 import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY;
+import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE;
+import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE;
 import static android.app.admin.ProvisioningException.ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_PRE_CONDITION_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_PROFILE_CREATION_FAILED;
@@ -112,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;
@@ -175,6 +190,7 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.app.admin.DevicePolicySafetyChecker;
+import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.DeviceStateCache;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
@@ -184,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;
@@ -230,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;
@@ -245,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;
@@ -310,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;
@@ -389,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";
@@ -1757,7 +1779,7 @@
      * Instantiates the service.
      */
     public DevicePolicyManagerService(Context context) {
-        this(new Injector(context));
+        this(new Injector(context.createAttributionContext(ATTRIBUTION_TAG)));
     }
 
     @VisibleForTesting
@@ -2234,7 +2256,7 @@
      * a managed profile.
      */
     @GuardedBy("getLockObject()")
-    private void applyManagedProfileRestrictionIfDeviceOwnerLocked() {
+    private void applyProfileRestrictionsIfDeviceOwnerLocked() {
         final int doUserId = mOwners.getDeviceOwnerUserId();
         if (doUserId == UserHandle.USER_NULL) {
             if (VERBOSE_LOG) Slogf.d(LOG_TAG, "No DO found, skipping application of restriction.");
@@ -2242,7 +2264,17 @@
         }
 
         final UserHandle doUserHandle = UserHandle.of(doUserId);
-        // Set the restriction if not set.
+
+        // Based on  CDD : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support,
+        // creation of clone profile is not allowed in case device owner is set.
+        // Enforcing this restriction on setting up of device owner.
+        if (!mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_ADD_CLONE_PROFILE, doUserHandle)) {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true,
+                    doUserHandle);
+        }
+        // Creation of managed profile is restricted in case device owner is set, enforcing this
+        // restriction by setting user level restriction at time of device owner setup.
         if (!mUserManager.hasUserRestriction(
                 UserManager.DISALLOW_ADD_MANAGED_PROFILE, doUserHandle)) {
             mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
@@ -3151,7 +3183,7 @@
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 synchronized (getLockObject()) {
                     migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
-                    applyManagedProfileRestrictionIfDeviceOwnerLocked();
+                    applyProfileRestrictionsIfDeviceOwnerLocked();
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -3332,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");
     }
@@ -3356,7 +3388,7 @@
 
     @Override
     void handleStopUser(int userId) {
-        updateNetworkPreferenceForUser(userId, false);
+        updateNetworkPreferenceForUser(userId, PreferentialNetworkServiceConfig.DEFAULT);
         stopOwnerService(userId, "stop-user");
     }
 
@@ -3776,6 +3808,12 @@
             mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
                     userHandle);
         }
+        // When a device owner is set, the system automatically restricts adding a clone profile.
+        // Remove this restriction when the device owner is cleared.
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, userHandle)) {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false,
+                    userHandle);
+        }
     }
 
     /**
@@ -6927,12 +6965,8 @@
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA);
 
         if (TextUtils.isEmpty(wipeReasonForUser)) {
-            if (calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance) {
-                wipeReasonForUser = mContext.getString(R.string.device_ownership_relinquished);
-            } else {
-                wipeReasonForUser = mContext.getString(
-                        R.string.work_profile_deleted_description_dpm_wipe);
-            }
+            wipeReasonForUser = getGenericWipeReason(
+                    calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
         }
 
         int userId = admin != null ? admin.getUserHandle().getIdentifier()
@@ -6983,6 +7017,18 @@
         wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId);
     }
 
+    private String getGenericWipeReason(
+            boolean calledByProfileOwnerOnOrgOwnedDevice, boolean calledOnParentInstance) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance
+                ? dpm.getString(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE,
+                        () -> mContext.getString(
+                                R.string.device_ownership_relinquished))
+                : dpm.getString(WORK_PROFILE_DELETED_GENERIC_MESSAGE,
+                        () -> mContext.getString(
+                                R.string.work_profile_deleted_description_dpm_wipe));
+    }
+
     /**
      * Clears device wide policies enforced by COPE PO when relinquishing the device. This method
      * should be invoked once the admin is gone, so that all methods that rely on calculating
@@ -7067,7 +7113,7 @@
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
-                        .setContentTitle(mContext.getString(R.string.work_profile_deleted))
+                        .setContentTitle(getWorkProfileDeletedTitle())
                         .setContentText(wipeReasonForUser)
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
                         .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
@@ -7075,6 +7121,12 @@
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
     }
 
+    private String getWorkProfileDeletedTitle() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(WORK_PROFILE_DELETED_TITLE,
+                () -> mContext.getString(R.string.work_profile_deleted));
+    }
+
     private void clearWipeProfileNotification() {
         mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PROFILE_WIPED);
     }
@@ -7164,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.
@@ -7305,12 +7415,10 @@
             // able to do so).
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
-                String wipeReasonForUser = mContext.getString(
-                        R.string.work_profile_deleted_reason_maximum_password_failure);
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
                         /*flags=*/ 0,
                         /*reason=*/ "reportFailedPasswordAttempt()",
-                        wipeReasonForUser,
+                        getFailedPasswordAttemptWipeMessage(),
                         userId);
             } catch (SecurityException e) {
                 Slogf.w(LOG_TAG, "Failed to wipe user " + userId
@@ -7324,6 +7432,13 @@
         }
     }
 
+    private String getFailedPasswordAttemptWipeMessage() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE,
+                () -> mContext.getString(
+                        R.string.work_profile_deleted_reason_maximum_password_failure));
+    }
+
     /**
      * Returns which user should be wiped if this admin's maximum filed password attempts policy is
      * violated.
@@ -8468,6 +8583,12 @@
                 // on the primary profile).
                 mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
                         UserHandle.of(userId));
+                // Restrict adding a clone profile when a device owner is set on the device.
+                // That is to prevent the co-existence of a clone profile and a device owner
+                // on the same device.
+                // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support
+                mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true,
+                        UserHandle.of(userId));
                 // TODO Send to system too?
                 sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
             });
@@ -8940,7 +9061,7 @@
         mOwners.writeProfileOwner(userId);
         deleteTransferOwnershipBundleLocked(userId);
         toggleBackupServiceActive(userId, true);
-        applyManagedProfileRestrictionIfDeviceOwnerLocked();
+        applyProfileRestrictionsIfDeviceOwnerLocked();
         setNetworkLoggingActiveInternal(false);
     }
 
@@ -12049,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());
@@ -12086,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");
@@ -12395,8 +12557,8 @@
         Notification notification = new Notification.Builder(mContext,
                 SystemNotificationChannels.DEVICE_ADMIN)
                 .setSmallIcon(R.drawable.ic_info_outline)
-                .setContentTitle(mContext.getString(R.string.location_changed_notification_title))
-                .setContentText(mContext.getString(R.string.location_changed_notification_text))
+                .setContentTitle(getLocationChangedTitle())
+                .setContentText(getLocationChangedText())
                 .setColor(mContext.getColor(R.color.system_notification_accent_color))
                 .setShowWhen(true)
                 .setContentIntent(locationSettingsIntent)
@@ -12406,6 +12568,18 @@
                 notification);
     }
 
+    private String getLocationChangedTitle() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(LOCATION_CHANGED_TITLE,
+                () -> mContext.getString(R.string.location_changed_notification_title));
+    }
+
+    private String getLocationChangedText() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(LOCATION_CHANGED_MESSAGE,
+                () -> mContext.getString(R.string.location_changed_notification_text));
+    }
+
     @Override
     public boolean setTime(ComponentName who, long millis) {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -12996,11 +13170,19 @@
                     Slogf.e(LOG_TAG, "appLabel is inexplicably null");
                     return null;
                 }
-                return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
-                        .getResources().getString(R.string.printing_disabled_by, appLabel);
+                DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+                return dpm.getString(
+                        PRINTING_DISABLED_NAMED_ADMIN,
+                        () -> getDefaultPrintingDisabledMsg(appLabel),
+                        appLabel);
             }
         }
 
+        private String getDefaultPrintingDisabledMsg(CharSequence appLabel) {
+            return ((Context) ActivityThread.currentActivityThread().getSystemUiContext())
+                        .getResources().getString(R.string.printing_disabled_by, appLabel);
+        }
+
         @Override
         protected DevicePolicyCache getDevicePolicyCache() {
             return mPolicyCache;
@@ -15532,16 +15714,18 @@
         // Simple notification clicks are immutable
         final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent,
                 PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+
+        final String title = getNetworkLoggingTitle();
+        final String text = getNetworkLoggingText();
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                 .setSmallIcon(R.drawable.ic_info_outline)
-                .setContentTitle(mContext.getString(R.string.network_logging_notification_title))
-                .setContentText(mContext.getString(R.string.network_logging_notification_text))
-                .setTicker(mContext.getString(R.string.network_logging_notification_title))
+                .setContentTitle(title)
+                .setContentText(text)
+                .setTicker(title)
                 .setShowWhen(true)
                 .setContentIntent(pendingIntent)
-                .setStyle(new Notification.BigTextStyle()
-                        .bigText(mContext.getString(R.string.network_logging_notification_text)))
+                .setStyle(new Notification.BigTextStyle().bigText(text))
                 .build();
         Slogf.i(LOG_TAG, "Sending network logging notification to user %d",
                 mNetworkLoggingNotificationUserId);
@@ -15550,6 +15734,18 @@
                 UserHandle.of(mNetworkLoggingNotificationUserId));
     }
 
+    private String getNetworkLoggingTitle() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(NETWORK_LOGGING_TITLE,
+                () -> mContext.getString(R.string.network_logging_notification_title));
+    }
+
+    private String getNetworkLoggingText() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(NETWORK_LOGGING_MESSAGE,
+                () -> mContext.getString(R.string.network_logging_notification_text));
+    }
+
     private void handleCancelNetworkLoggingNotification() {
         if (mNetworkLoggingNotificationUserId == UserHandle.USER_NULL) {
             // Happens when setNetworkLoggingActive(false) is called before called with true
@@ -17042,10 +17238,8 @@
                 0 /* requestCode */, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
-        final String buttonText =
-                mContext.getString(R.string.personal_apps_suspended_turn_profile_on);
-        final Notification.Action turnProfileOnButton =
-                new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build();
+        final Notification.Action turnProfileOnButton = new Notification.Action.Builder(
+                /* icon= */ null, getPersonalAppSuspensionButtonText(), pendingIntent).build();
 
         final String text;
         final boolean ongoing;
@@ -17057,26 +17251,24 @@
                     mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE);
             final String time = DateUtils.formatDateTime(
                     mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME);
-            text = mContext.getString(
-                    R.string.personal_apps_suspension_soon_text, date, time, maxDays);
+            text = getPersonalAppSuspensionSoonText(date, time, maxDays);
             ongoing = false;
         } else {
-            text = mContext.getString(R.string.personal_apps_suspension_text);
+            text = getPersonalAppSuspensionText();
             ongoing = true;
         }
         final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color);
         final Bundle extras = new Bundle();
         // TODO: Create a separate string for this.
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                mContext.getString(R.string.notification_work_profile_content_description));
+        extras.putString(
+                Notification.EXTRA_SUBSTITUTE_APP_NAME, getWorkProfileContentDescription());
 
         final Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(R.drawable.ic_corp_badge_no_background)
                         .setOngoing(ongoing)
                         .setAutoCancel(false)
-                        .setContentTitle(mContext.getString(
-                                R.string.personal_apps_suspension_title))
+                        .setContentTitle(getPersonalAppSuspensionTitle())
                         .setContentText(text)
                         .setStyle(new Notification.BigTextStyle().bigText(text))
                         .setColor(color)
@@ -17087,6 +17279,38 @@
                 SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification);
     }
 
+    private String getPersonalAppSuspensionButtonText() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE,
+                () -> mContext.getString(R.string.personal_apps_suspended_turn_profile_on));
+    }
+
+    private String getPersonalAppSuspensionTitle() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE,
+                () -> mContext.getString(R.string.personal_apps_suspension_title));
+    }
+
+    private String getPersonalAppSuspensionText() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE,
+                () -> mContext.getString(R.string.personal_apps_suspension_text));
+    }
+
+    private String getPersonalAppSuspensionSoonText(String date, String time, int maxDays) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE,
+                () -> mContext.getString(
+                        R.string.personal_apps_suspension_soon_text, date, time, maxDays),
+                date, time, maxDays);
+    }
+
+    private String getWorkProfileContentDescription() {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        return dpm.getString(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION,
+                () -> mContext.getString(R.string.notification_work_profile_content_description));
+    }
+
     @Override
     public void setManagedProfileMaximumTimeOff(ComponentName who, long timeoutMillis) {
         Objects.requireNonNull(who, "ComponentName is null");
@@ -17846,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(() ->
@@ -17977,6 +18238,117 @@
         );
     }
 
+    private void validateCurrentWifiMeetsAdminRequirements() {
+        mInjector.binderWithCleanCallingIdentity(
+                () -> mInjector.getWifiManager().validateCurrentWifiMeetsAdminRequirements());
+    }
+
+    @Override
+    public void setMinimumRequiredWifiSecurityLevel(int level) {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "Wi-Fi minimum security level can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+
+        boolean valueChanged = false;
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            if (admin.mWifiMinimumSecurityLevel != level) {
+                admin.mWifiMinimumSecurityLevel = level;
+                saveSettingsLocked(caller.getUserId());
+                valueChanged = true;
+            }
+        }
+        if (valueChanged) validateCurrentWifiMeetsAdminRequirements();
+    }
+
+    @Override
+    public int getMinimumRequiredWifiSecurityLevel() {
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN
+                    : admin.mWifiMinimumSecurityLevel;
+        }
+    }
+
+    @Override
+    public void setSsidAllowlist(List<String> ssids) {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "SSID allowlist can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+
+        Collections.sort(ssids);
+        boolean changed = false;
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            if (!ssids.equals(admin.mSsidAllowlist)) {
+                admin.mSsidAllowlist = ssids;
+                admin.mSsidDenylist = null;
+                changed = true;
+            }
+            if (changed) saveSettingsLocked(caller.getUserId());
+        }
+        if (changed) validateCurrentWifiMeetsAdminRequirements();
+    }
+
+    @Override
+    public List<String> getSsidAllowlist() {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isSystemUid(caller),
+                "SSID allowlist can only be retrieved by a device owner or "
+                        + "a profile owner on an organization-owned device or a system app.");
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            return (admin == null || admin.mSsidAllowlist == null) ? new ArrayList<>()
+                    : admin.mSsidAllowlist;
+        }
+    }
+
+    @Override
+    public void setSsidDenylist(List<String> ssids) {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "SSID denylist can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
+
+        Collections.sort(ssids);
+        boolean changed = false;
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
+            if (!ssids.equals(admin.mSsidDenylist)) {
+                admin.mSsidDenylist = ssids;
+                admin.mSsidAllowlist = null;
+                changed = true;
+            }
+            if (changed) saveSettingsLocked(caller.getUserId());
+        }
+        if (changed) validateCurrentWifiMeetsAdminRequirements();
+    }
+
+    @Override
+    public List<String> getSsidDenylist() {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isSystemUid(caller),
+                "SSID denylist can only be retrieved by a device owner or "
+                        + "a profile owner on an organization-owned device or a system app.");
+        synchronized (getLockObject()) {
+            final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                    UserHandle.USER_SYSTEM);
+            return (admin == null || admin.mSsidDenylist == null) ? new ArrayList<>()
+                    : admin.mSsidDenylist;
+        }
+    }
+
     @Override
     public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
         Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
@@ -17987,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));
 
@@ -18007,16 +18379,57 @@
     }
 
     @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) {
+    private void sendDrawableUpdatedBroadcast(String[] drawableIds) {
+        sendResourceUpdatedBroadcast(EXTRA_RESOURCE_TYPE_DRAWABLE, drawableIds);
+    }
+
+    @Override
+    public void setStrings(@NonNull List<DevicePolicyStringResource> strings) {
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+
+        Objects.requireNonNull(strings, "strings must be provided.");
+
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            if (mDeviceManagementResourcesProvider.updateStrings(strings))
+            sendStringsUpdatedBroadcast(
+                    strings.stream().map(s -> s.getStringId()).toArray(String[]::new));
+        });
+    }
+
+    @Override
+    public void resetStrings(String[] stringIds) {
+        Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES));
+
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            if (mDeviceManagementResourcesProvider.removeStrings(stringIds)) {
+                sendStringsUpdatedBroadcast(stringIds);
+            }
+        });
+    }
+
+    @Override
+    public ParcelableResource getString(String stringId) {
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                mDeviceManagementResourcesProvider.getString(stringId));
+    }
+
+    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, drawableIds);
-        intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* 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 4b21454..1fe71f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -54,6 +54,7 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityModuleConnector;
 import android.net.NetworkStackClient;
+import android.net.TrafficStats;
 import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
@@ -103,6 +104,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.ambientcontext.AmbientContextManagerService;
 import com.android.server.appbinding.AppBindingService;
 import com.android.server.art.ArtManagerLocal;
 import com.android.server.attention.AttentionManagerService;
@@ -136,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;
@@ -194,7 +197,7 @@
 import com.android.server.trust.TrustManagerService;
 import com.android.server.tv.TvInputManagerService;
 import com.android.server.tv.TvRemoteService;
-import com.android.server.tv.interactive.TvIAppManagerService;
+import com.android.server.tv.interactive.TvInteractiveAppManagerService;
 import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.uri.UriGrantsManagerService;
@@ -261,6 +264,8 @@
             "/apex/com.android.os.statsd/javalib/service-statsd.jar";
     private static final String CONNECTIVITY_SERVICE_APEX_PATH =
             "/apex/com.android.tethering/javalib/service-connectivity.jar";
+    private static final String NEARBY_SERVICE_APEX_PATH =
+            "/apex/com.android.nearby/javalib/service-nearby.jar";
     private static final String STATS_COMPANION_LIFECYCLE_CLASS =
             "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String STATS_PULL_ATOM_SERVICE_CLASS =
@@ -271,6 +276,8 @@
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
             "com.android.server.midi.MidiService$Lifecycle";
+    private static final String NEARBY_SERVICE_CLASS =
+            "com.android.server.nearby.NearbyService";
     private static final String WIFI_APEX_SERVICE_JAR_PATH =
             "/apex/com.android.wifi/javalib/service-wifi.jar";
     private static final String WIFI_SERVICE_CLASS =
@@ -403,8 +410,6 @@
     private static final String SAFETY_CENTER_SERVICE_CLASS =
             "com.android.safetycenter.SafetyCenterService";
 
-    private static final String SUPPLEMENTALPROCESS_APEX_PATH =
-            "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar";
     private static final String SUPPLEMENTALPROCESS_SERVICE_CLASS =
             "com.android.server.supplementalprocess.SupplementalProcessManagerService$Lifecycle";
 
@@ -1627,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");
@@ -1807,6 +1816,7 @@
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
+            startAmbientContextService(t);
 
             // System Speech Recognition Service
             t.traceBegin("StartSpeechRecognitionManagerService");
@@ -1901,6 +1911,7 @@
             try {
                 networkStats = NetworkStatsService.create(context);
                 ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+                TrafficStats.init(context);
             } catch (Throwable e) {
                 reportWtf("starting NetworkStats Service", e);
             }
@@ -1976,6 +1987,16 @@
             }
             t.traceEnd();
 
+            // Start Nearby Service.
+            t.traceBegin("StartNearbyService");
+            try {
+                mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS,
+                        NEARBY_SERVICE_APEX_PATH);
+            } catch (Throwable e) {
+                reportWtf("starting NearbyService", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartConnectivityService");
             // This has to be called after NetworkManagementService, NetworkStatsService
             // and NetworkPolicyManager because ConnectivityService needs to take these
@@ -2367,8 +2388,8 @@
 
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LIVE_TV)
                     || mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-                t.traceBegin("StartTvIAppManager");
-                mSystemServiceManager.startService(TvIAppManagerService.class);
+                t.traceBegin("StartTvInteractiveAppManager");
+                mSystemServiceManager.startService(TvInteractiveAppManagerService.class);
                 t.traceEnd();
             }
 
@@ -2558,8 +2579,7 @@
 
         // Supplemental Process
         t.traceBegin("StartSupplementalProcessManagerService");
-        mSystemServiceManager.startServiceFromJar(SUPPLEMENTALPROCESS_SERVICE_CLASS,
-                SUPPLEMENTALPROCESS_APEX_PATH);
+        mSystemServiceManager.startService(SUPPLEMENTALPROCESS_SERVICE_CLASS);
         t.traceEnd();
 
         if (safeMode) {
@@ -3149,6 +3169,17 @@
 
     }
 
+    private void startAmbientContextService(@NonNull TimingsTraceAndSlog t) {
+        if (!AmbientContextManagerService.isDetectionServiceConfigured()) {
+            Slog.d(TAG, "AmbientContextDetectionService is not configured on this device");
+            return;
+        }
+
+        t.traceBegin("StartAmbientContextService");
+        mSystemServiceManager.startService(AmbientContextManagerService.class);
+        t.traceEnd();
+    }
+
     private static void startSystemUi(Context context, WindowManagerService windowManager) {
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
         Intent intent = new Intent();
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index ca31efc..d562786 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -18,9 +18,11 @@
 
 import android.annotation.NonNull;
 import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -33,6 +35,7 @@
 import android.media.midi.IMidiDeviceOpenCallback;
 import android.media.midi.IMidiDeviceServer;
 import android.media.midi.IMidiManager;
+import android.media.midi.MidiDevice;
 import android.media.midi.MidiDeviceInfo;
 import android.media.midi.MidiDeviceService;
 import android.media.midi.MidiDeviceStatus;
@@ -55,6 +58,7 @@
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -96,9 +100,12 @@
             = new HashMap<MidiDeviceInfo, Device>();
 
     // list of all Bluetooth devices, keyed by BluetoothDevice
-     private final HashMap<BluetoothDevice, Device> mBluetoothDevices
+    private final HashMap<BluetoothDevice, Device> mBluetoothDevices
             = new HashMap<BluetoothDevice, Device>();
 
+    private final HashMap<BluetoothDevice, MidiDevice> mBleMidiDeviceMap =
+            new HashMap<BluetoothDevice, MidiDevice>();
+
     // list of all devices, keyed by IMidiDeviceServer
     private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
 
@@ -569,10 +576,45 @@
         }
     }
 
+    private final BroadcastReceiver mBleMidiReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                Log.w(TAG, "MidiService, action is null");
+                return;
+            }
+
+            switch (action) {
+                case BluetoothDevice.ACTION_ACL_CONNECTED: {
+                    Log.d(TAG, "ACTION_ACL_CONNECTED");
+                    BluetoothDevice btDevice =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    openBluetoothDevice(btDevice);
+                }
+                break;
+
+                case BluetoothDevice.ACTION_ACL_DISCONNECTED: {
+                    Log.d(TAG, "ACTION_ACL_DISCONNECTED");
+                    BluetoothDevice btDevice =
+                            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                    closeBluetoothDevice(btDevice);
+                }
+                break;
+            }
+        }
+    };
+
     public MidiService(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
 
+        // Setup broadcast receivers
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        context.registerReceiver(mBleMidiReceiver, filter);
+
         mBluetoothServiceUid = -1;
     }
 
@@ -643,13 +685,31 @@
     private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
 
     public MidiDeviceInfo[] getDevices() {
+        return getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+    }
+
+    /**
+    * @hide
+    */
+    public MidiDeviceInfo[] getDevicesForTransport(int transport) {
         ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
         int uid = Binder.getCallingUid();
 
         synchronized (mDevicesByInfo) {
             for (Device device : mDevicesByInfo.values()) {
                 if (device.isUidAllowed(uid)) {
-                    deviceInfos.add(device.getDeviceInfo());
+                    // UMP devices have protocols that are not PROTOCOL_UNKNOWN
+                    if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
+                        if (device.getDeviceInfo().getDefaultProtocol()
+                                != MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+                            deviceInfos.add(device.getDeviceInfo());
+                        }
+                    } else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) {
+                        if (device.getDeviceInfo().getDefaultProtocol()
+                                == MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+                            deviceInfos.add(device.getDeviceInfo());
+                        }
+                    }
                 }
             }
         }
@@ -683,9 +743,43 @@
         }
     }
 
+    private void openBluetoothDevice(BluetoothDevice bluetoothDevice) {
+        Log.d(TAG, "openBluetoothDevice() device: " + bluetoothDevice);
+
+        MidiManager midiManager = mContext.getSystemService(MidiManager.class);
+        midiManager.openBluetoothDevice(bluetoothDevice,
+                new MidiManager.OnDeviceOpenedListener() {
+                    @Override
+                    public void onDeviceOpened(MidiDevice device) {
+                        synchronized (mBleMidiDeviceMap) {
+                            mBleMidiDeviceMap.put(bluetoothDevice, device);
+                        }
+                    }
+                }, null);
+    }
+
+    private void closeBluetoothDevice(BluetoothDevice bluetoothDevice) {
+        Log.d(TAG, "closeBluetoothDevice() device: " + bluetoothDevice);
+
+        MidiDevice midiDevice;
+        synchronized (mBleMidiDeviceMap) {
+            midiDevice = mBleMidiDeviceMap.remove(bluetoothDevice);
+        }
+
+        if (midiDevice != null) {
+            try {
+                midiDevice.close();
+            } catch (IOException ex) {
+                Log.e(TAG, "Exception closing BLE-MIDI device" + ex);
+            }
+        }
+    }
+
     @Override
     public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice,
             IMidiDeviceOpenCallback callback) {
+        Log.d(TAG, "openBluetoothDevice()");
+
         Client client = getClient(token);
         if (client == null) return;
 
@@ -718,7 +812,7 @@
     @Override
     public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
-            Bundle properties, int type) {
+            Bundle properties, int type, int defaultProtocol) {
         int uid = Binder.getCallingUid();
         if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
             throw new SecurityException("only system can create USB devices");
@@ -728,7 +822,8 @@
 
         synchronized (mDevicesByInfo) {
             return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
-                    outputPortNames, properties, server, null, false, uid);
+                    outputPortNames, properties, server, null, false, uid,
+                    defaultProtocol);
         }
     }
 
@@ -769,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
@@ -797,11 +900,12 @@
     private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
             String[] inputPortNames, String[] outputPortNames, Bundle properties,
             IMidiDeviceServer server, ServiceInfo serviceInfo,
-            boolean isPrivate, int uid) {
+            boolean isPrivate, int uid, int defaultProtocol) {
 
         int id = mNextDeviceId++;
         MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
-                inputPortNames, outputPortNames, properties, isPrivate);
+                inputPortNames, outputPortNames, properties, isPrivate,
+                defaultProtocol);
 
         if (server != null) {
             try {
@@ -988,10 +1092,11 @@
 
                             synchronized (mDevicesByInfo) {
                                 addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
-                                    numInputPorts, numOutputPorts,
-                                    inputPortNames.toArray(EMPTY_STRING_ARRAY),
-                                    outputPortNames.toArray(EMPTY_STRING_ARRAY),
-                                    properties, null, serviceInfo, isPrivate, uid);
+                                        numInputPorts, numOutputPorts,
+                                        inputPortNames.toArray(EMPTY_STRING_ARRAY),
+                                        outputPortNames.toArray(EMPTY_STRING_ARRAY),
+                                        properties, null, serviceInfo, isPrivate, uid,
+                                        MidiDeviceInfo.PROTOCOL_UNKNOWN);
                             }
                             // setting properties to null signals that we are no longer
                             // processing a <device>
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/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index a0f3bbf..cc663d9 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -503,6 +503,11 @@
                     PackageManager.Property::getString
                 )
             }
+        ),
+        getSetByValue(
+            AndroidPackage::shouldInheritKeyStoreKeys,
+            ParsingPackage::setInheritKeyStoreKeys,
+            true
         )
     )
 
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/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
new file mode 100644
index 0000000..fec9b12
--- /dev/null
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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.games;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.graphics.Bitmap;
+import android.platform.test.annotations.Presubmit;
+import android.service.games.GameSession.ScreenshotCallback;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControlViewHost;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for the {@link android.service.games.GameSession}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@Presubmit
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public final class GameSessionTest {
+    private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
+    private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+
+    @Mock
+    private IGameSessionController mMockGameSessionController;
+    @Mock
+    SurfaceControlViewHost mSurfaceControlViewHost;
+    private LifecycleTrackingGameSession mGameSession;
+    private MockitoSession mMockitoSession;
+
+    @Before
+    public void setUp() {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .startMocking();
+
+        mGameSession = new LifecycleTrackingGameSession() {};
+        mGameSession.attach(mMockGameSessionController, /* taskId= */ 10,
+                InstrumentationRegistry.getContext(),
+                mSurfaceControlViewHost,
+                /* widthPx= */ 0, /* heightPx= */0);
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void takeScreenshot_attachNotCalled_throwsIllegalStateException() throws Exception {
+        GameSession gameSession = new GameSession() {};
+
+        try {
+            gameSession.takeScreenshot(DIRECT_EXECUTOR,
+                    new ScreenshotCallback() {
+                        @Override
+                        public void onFailure(int statusCode) {
+                            fail();
+                        }
+
+                        @Override
+                        public void onSuccess(Bitmap bitmap) {
+                            fail();
+                        }
+                    });
+            fail();
+        } catch (IllegalStateException expected) {
+
+        }
+    }
+
+    @Test
+    public void takeScreenshot_gameManagerException_returnsInternalError() throws Exception {
+        doAnswer(invocation -> {
+            AndroidFuture result = invocation.getArgument(1);
+            result.completeExceptionally(new Exception());
+            return null;
+        }).when(mMockGameSessionController).takeScreenshot(anyInt(), any());
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+
+        mGameSession.takeScreenshot(DIRECT_EXECUTOR,
+                new ScreenshotCallback() {
+                    @Override
+                    public void onFailure(int statusCode) {
+                        assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                statusCode);
+                        countDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onSuccess(Bitmap bitmap) {
+                        fail();
+                    }
+                });
+
+        assertTrue(countDownLatch.await(
+                WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void takeScreenshot_gameManagerError_returnsInternalError() throws Exception {
+        doAnswer(invocation -> {
+            AndroidFuture result = invocation.getArgument(1);
+            result.complete(GameScreenshotResult.createInternalErrorResult());
+            return null;
+        }).when(mMockGameSessionController).takeScreenshot(anyInt(), any());
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+
+        mGameSession.takeScreenshot(DIRECT_EXECUTOR,
+                new ScreenshotCallback() {
+                    @Override
+                    public void onFailure(int statusCode) {
+                        assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                statusCode);
+                        countDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onSuccess(Bitmap bitmap) {
+                        fail();
+                    }
+                });
+
+        assertTrue(countDownLatch.await(
+                WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void takeScreenshot_gameManagerSuccess_returnsBitmap() throws Exception {
+        doAnswer(invocation -> {
+            AndroidFuture result = invocation.getArgument(1);
+            result.complete(GameScreenshotResult.createSuccessResult(TEST_BITMAP));
+            return null;
+        }).when(mMockGameSessionController).takeScreenshot(anyInt(), any());
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+
+        mGameSession.takeScreenshot(DIRECT_EXECUTOR,
+                new ScreenshotCallback() {
+                    @Override
+                    public void onFailure(int statusCode) {
+                        fail();
+                    }
+
+                    @Override
+                    public void onSuccess(Bitmap bitmap) {
+                        assertEquals(TEST_BITMAP, bitmap);
+                        countDownLatch.countDown();
+                    }
+                });
+
+        assertTrue(countDownLatch.await(
+                WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void moveState_InitializedToInitialized_noLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.INITIALIZED);
+
+        assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void moveState_FullLifecycle_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.DESTROYED);
+
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
+    }
+
+    @Test
+    public void moveState_DestroyedWhenInitialized_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.DESTROYED);
+
+        // ON_CREATE is always called before ON_DESTROY.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
+    }
+
+    @Test
+    public void moveState_DestroyedWhenFocused_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.DESTROYED);
+
+        // The ON_GAME_TASK_UNFOCUSED lifecycle event is implied because the session is destroyed
+        // while in focus.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
+    }
+
+    @Test
+    public void moveState_FocusCycled_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+
+        // Both cycles from focus and unfocus are captured.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder();
+    }
+
+    @Test
+    public void moveState_MultipleFocusAndUnfocusCalls_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+
+        // The second TASK_FOCUSED call and the second TASK_UNFOCUSED call are ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder();
+    }
+
+    @Test
+    public void moveState_CreatedAfterFocused_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+
+        // The second CREATED call is ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder();
+    }
+
+    @Test
+    public void moveState_UnfocusedWithoutFocused_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+
+        // The TASK_UNFOCUSED call without an earlier TASK_FOCUSED call is ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder();
+    }
+
+    @Test
+    public void moveState_NeverFocused_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.DESTROYED);
+
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
+    }
+
+    @Test
+    public void moveState_MultipleFocusCalls_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+
+        // The extra TASK_FOCUSED moves are ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder();
+    }
+
+    @Test
+    public void moveState_MultipleCreateCalls_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+
+        // The extra CREATE moves are ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder();
+    }
+
+    @Test
+    public void moveState_FocusBeforeCreate_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+
+        // The TASK_FOCUSED move before CREATE is ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void moveState_UnfocusBeforeCreate_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED);
+
+        // The TASK_UNFOCUSED move before CREATE is ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue();
+    }
+
+    @Test
+    public void moveState_FocusWhenDestroyed_ExpectedLifecycleCalls() throws Exception {
+        mGameSession.moveToState(GameSession.LifecycleState.CREATED);
+        mGameSession.moveToState(GameSession.LifecycleState.DESTROYED);
+        mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED);
+
+        // The TASK_FOCUSED move after DESTROYED is ignored.
+        assertThat(mGameSession.mLifecycleMethodCalls).containsExactly(
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE,
+                LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder();
+    }
+
+    private static class LifecycleTrackingGameSession extends GameSession {
+        private enum LifecycleMethodCall {
+            ON_CREATE,
+            ON_DESTROY,
+            ON_GAME_TASK_FOCUSED,
+            ON_GAME_TASK_UNFOCUSED
+        }
+
+        final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>();
+
+        @Override
+        public void onCreate() {
+            mLifecycleMethodCalls.add(LifecycleMethodCall.ON_CREATE);
+        }
+
+        @Override
+        public void onDestroy() {
+            mLifecycleMethodCalls.add(LifecycleMethodCall.ON_DESTROY);
+        }
+
+        @Override
+        public void onGameTaskFocusChanged(boolean focused) {
+            if (focused) {
+                mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_FOCUSED);
+            } else {
+                mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED);
+            }
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index d6705a5..0198253 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -28,6 +29,7 @@
 
 import android.Manifest;
 import android.app.GameManager;
+import android.app.GameModeInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
@@ -515,7 +517,7 @@
     public void testDeviceConfigDefault() {
         mockDeviceConfigDefault();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
+        checkReportedModes(null);
     }
 
     /**
@@ -525,7 +527,7 @@
     public void testDeviceConfigNone() {
         mockDeviceConfigNone();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
+        checkReportedModes(null);
     }
 
     /**
@@ -566,7 +568,7 @@
     public void testDeviceConfigInvalid() {
         mockDeviceConfigInvalid();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
+        checkReportedModes(null);
     }
 
     /**
@@ -576,7 +578,7 @@
     public void testDeviceConfigMalformed() {
         mockDeviceConfigMalformed();
         mockModifyGameModeGranted();
-        checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED);
+        checkReportedModes(null);
     }
 
     /**
@@ -966,4 +968,84 @@
     static {
         System.loadLibrary("mockingservicestestjni");
     }
+    @Test
+    public void testGetGameModeInfoPermissionDenied() {
+        mockDeviceConfigAll();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+
+        // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+        mockModifyGameModeDenied();
+        assertThrows(SecurityException.class,
+                () -> gameManagerService.getGameModeInfo(mPackageName, USER_ID_1));
+    }
+
+    @Test
+    public void testGetGameModeInfoWithAllGameModesDefault() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
+        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
+    }
+
+    @Test
+    public void testGetGameModeInfoWithAllGameModes() {
+        mockDeviceConfigAll();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
+        assertEquals(3, gameModeInfo.getAvailableGameModes().length);
+    }
+
+    @Test
+    public void testGetGameModeInfoWithBatteryMode() {
+        mockDeviceConfigBattery();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode());
+        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+    }
+
+    @Test
+    public void testGetGameModeInfoWithPerformanceMode() {
+        mockDeviceConfigPerformance();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode());
+        assertEquals(2, gameModeInfo.getAvailableGameModes().length);
+    }
+
+    @Test
+    public void testGetGameModeInfoWithUnsupportedGameMode() {
+        mockDeviceConfigNone();
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        startUser(gameManagerService, USER_ID_1);
+        GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode());
+        assertEquals(0, gameModeInfo.getAvailableGameModes().length);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 0d513bb..bdfa3bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -18,29 +18,43 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGameService.GameServiceState;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.app.ITaskStackListener;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
-import android.os.IBinder;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.service.games.CreateGameSessionRequest;
+import android.service.games.CreateGameSessionResult;
+import android.service.games.GameScreenshotResult;
+import android.service.games.GameSessionViewHostConfiguration;
 import android.service.games.GameStartedEvent;
 import android.service.games.IGameService;
 import android.service.games.IGameServiceController;
 import android.service.games.IGameSession;
+import android.service.games.IGameSessionController;
 import android.service.games.IGameSessionService;
+import android.view.SurfaceControlViewHost.SurfacePackage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -48,20 +62,21 @@
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+import com.android.internal.util.Preconditions;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerService;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
+import java.util.HashMap;
 
 
 /**
@@ -72,6 +87,9 @@
 @Presubmit
 public final class GameServiceProviderInstanceImplTest {
 
+    private static final GameSessionViewHostConfiguration
+            DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION =
+            new GameSessionViewHostConfiguration(1, 500, 800);
     private static final int USER_ID = 10;
     private static final String APP_A_PACKAGE = "com.package.app.a";
     private static final ComponentName APP_A_MAIN_ACTIVITY =
@@ -81,19 +99,23 @@
     private static final ComponentName GAME_A_MAIN_ACTIVITY =
             new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity");
 
+    private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
+
     private MockitoSession mMockingSession;
     private GameServiceProviderInstance mGameServiceProviderInstance;
     @Mock
     private IActivityTaskManager mMockActivityTaskManager;
     @Mock
-    private IGameService mMockGameService;
+    private WindowManagerService mMockWindowManagerService;
     @Mock
-    private IGameSessionService mMockGameSessionService;
+    private WindowManagerInternal mMockWindowManagerInternal;
     private FakeGameClassifier mFakeGameClassifier;
+    private FakeGameService mFakeGameService;
     private FakeServiceConnector<IGameService> mFakeGameServiceConnector;
+    private FakeGameSessionService mFakeGameSessionService;
     private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector;
     private ArrayList<ITaskStackListener> mTaskStackListeners;
-    private InOrder mInOrder;
+    private ArrayList<RunningTaskInfo> mRunningTaskInfos;
 
     @Before
     public void setUp() throws PackageManager.NameNotFoundException, RemoteException {
@@ -102,13 +124,13 @@
                 .strictness(Strictness.LENIENT)
                 .startMocking();
 
-        mInOrder = inOrder(mMockGameService, mMockGameSessionService);
-
         mFakeGameClassifier = new FakeGameClassifier();
         mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE);
 
-        mFakeGameServiceConnector = new FakeServiceConnector<>(mMockGameService);
-        mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mMockGameSessionService);
+        mFakeGameService = new FakeGameService();
+        mFakeGameServiceConnector = new FakeServiceConnector<>(mFakeGameService);
+        mFakeGameSessionService = new FakeGameSessionService();
+        mFakeGameSessionServiceConnector = new FakeServiceConnector<>(mFakeGameSessionService);
 
         mTaskStackListeners = new ArrayList<>();
         doAnswer(invocation -> {
@@ -116,6 +138,10 @@
             return null;
         }).when(mMockActivityTaskManager).registerTaskStackListener(any());
 
+        mRunningTaskInfos = new ArrayList<>();
+        when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn(
+                mRunningTaskInfos);
+
         doAnswer(invocation -> {
             mTaskStackListeners.remove(invocation.getArgument(0));
             return null;
@@ -126,6 +152,8 @@
                 ConcurrentUtils.DIRECT_EXECUTOR,
                 mFakeGameClassifier,
                 mMockActivityTaskManager,
+                mMockWindowManagerService,
+                mMockWindowManagerInternal,
                 mFakeGameServiceConnector,
                 mFakeGameSessionServiceConnector);
     }
@@ -139,8 +167,7 @@
     public void start_startsGameSession() throws Exception {
         mGameServiceProviderInstance.start();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verifyNoMoreInteractions();
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED);
         assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
@@ -149,9 +176,9 @@
     @Test
     public void start_multipleTimes_startsGameSessionOnce() throws Exception {
         mGameServiceProviderInstance.start();
+        mGameServiceProviderInstance.start();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verifyNoMoreInteractions();
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED);
         assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
@@ -161,9 +188,10 @@
     public void stop_neverStarted_doesNothing() throws Exception {
         mGameServiceProviderInstance.stop();
 
+
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED);
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
-        mInOrder.verifyNoMoreInteractions();
     }
 
     @Test
@@ -171,9 +199,8 @@
         mGameServiceProviderInstance.start();
         mGameServiceProviderInstance.stop();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verifyNoMoreInteractions();
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED);
+        assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1);
         assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
@@ -187,11 +214,8 @@
         mGameServiceProviderInstance.start();
         mGameServiceProviderInstance.stop();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verifyNoMoreInteractions();
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED);
+        assertThat(mFakeGameService.getConnectedCount()).isEqualTo(2);
         assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(2);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
@@ -203,9 +227,8 @@
         mGameServiceProviderInstance.stop();
         mGameServiceProviderInstance.stop();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verifyNoMoreInteractions();
+        assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.DISCONNECTED);
+        assertThat(mFakeGameService.getConnectedCount()).isEqualTo(1);
         assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
@@ -215,7 +238,6 @@
     public void gameTaskStarted_neverStarted_doesNothing() throws Exception {
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
-        mInOrder.verifyNoMoreInteractions();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
     }
@@ -224,35 +246,25 @@
     public void gameTaskRemoved_neverStarted_doesNothing() throws Exception {
         dispatchTaskRemoved(10);
 
-        mInOrder.verifyNoMoreInteractions();
         assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(0);
         assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
     }
 
     @Test
-    public void gameTaskStarted_afterStopped_doesNothing() throws Exception {
+    public void gameTaskStarted_afterStopped_doesNotSendGameStartedEvent() throws Exception {
         mGameServiceProviderInstance.start();
         mGameServiceProviderInstance.stop();
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+        assertThat(mFakeGameService.getGameStartedEvents()).isEmpty();
     }
 
     @Test
-    public void appTaskStarted_doesNothing() throws Exception {
+    public void appTaskStarted_doesNotSendGameStartedEvent() throws Exception {
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, APP_A_MAIN_ACTIVITY);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+        assertThat(mFakeGameService.getGameStartedEvents()).isEmpty();
     }
 
     @Test
@@ -260,26 +272,17 @@
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, null);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+        assertThat(mFakeGameService.getGameStartedEvents()).isEmpty();
     }
 
     @Test
-    public void gameSessionRequested_withoutTaskDispatch_ignoredAndDoesNotCrash() throws Exception {
+    public void gameSessionRequested_withoutTaskDispatch_doesNotCrashAndDoesNotCreateGameSession()
+            throws Exception {
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        controllerArgumentCaptor.getValue().createGameSession(10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+        mFakeGameService.requestCreateGameSession(10);
+
+        assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
     }
 
     @Test
@@ -287,433 +290,410 @@
         mGameServiceProviderInstance.start();
         dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0);
+        GameStartedEvent expectedGameStartedEvent = new GameStartedEvent(10, GAME_A_PACKAGE);
+        assertThat(mFakeGameService.getGameStartedEvents())
+                .containsExactly(expectedGameStartedEvent).inOrder();
+    }
+
+    @Test
+    public void gameTaskStarted_requestToCreateGameSessionIncludesTaskConfiguration()
+            throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation =
+                getOnlyElement(mFakeGameSessionService.getCapturedCreateInvocations());
+        assertThat(capturedCreateInvocation.mGameSessionViewHostConfiguration)
+                .isEqualTo(DEFAULT_GAME_SESSION_VIEW_HOST_CONFIGURATION);
+    }
+
+    @Test
+    public void gameTaskStarted_failsToDetermineTaskOverlayConfiguration_gameSessionNotCreated()
+            throws Exception {
+        mGameServiceProviderInstance.start();
+        dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
+
+        mFakeGameService.requestCreateGameSession(10);
+
+        assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty();
     }
 
     @Test
     public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception {
-        CreateGameSessionRequest createGameSessionRequest =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
-        mInOrder.verifyNoMoreInteractions();
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
         assertThat(gameSession10.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+        assertThat(gameSession10.mIsFocused).isFalse();
     }
 
     @Test
     public void gameTaskStartedAndSessionRequested_secondSessionRequest_ignoredAndDoesNotCrash()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest);
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+
+        mFakeGameService.requestCreateGameSession(10);
+        mFakeGameService.requestCreateGameSession(10);
+
+        CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10,
+                GAME_A_PACKAGE);
+        assertThat(getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations()).mCreateGameSessionRequest)
+                .isEqualTo(expectedCreateGameSessionRequest);
+    }
+
+    @Test
+    public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
+        verifyNoMoreInteractions(mMockWindowManagerInternal);
+    }
+
+    @Test
+    public void gameTaskFocused_propagatedToGameSession() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        assertThat(gameSession10.mIsFocused).isFalse();
+
+        dispatchTaskFocused(10, /*focused=*/ true);
+        assertThat(gameSession10.mIsFocused).isTrue();
+
+        dispatchTaskFocused(10, /*focused=*/ false);
+        assertThat(gameSession10.mIsFocused).isFalse();
+    }
+
+    @Test
+    public void gameTaskAlreadyFocusedWhenGameSessionCreated_propagatedToGameSession()
+            throws Exception {
+        ActivityTaskManager.RootTaskInfo gameATaskInfo = new ActivityTaskManager.RootTaskInfo();
+        gameATaskInfo.taskId = 10;
+        when(mMockActivityTaskManager.getFocusedRootTaskInfo()).thenReturn(gameATaskInfo);
 
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
 
-        controllerArgumentCaptor.getValue().createGameSession(10);
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(gameSession10.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+        assertThat(gameSession10.mIsFocused).isTrue();
     }
 
     @Test
     public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        dispatchTaskRemoved(10);
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
-        mInOrder.verifyNoMoreInteractions();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        dispatchTaskRemoved(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
         assertThat(gameSession10.mIsDestroyed).isTrue();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
     }
 
     @Test
-    public void gameTaskRemoved_destroysGameSession() throws Exception {
-        CreateGameSessionRequest createGameSessionRequest =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest);
-
+    public void gameTaskRemoved_whileGameSessionAttached_destroysGameSession() throws Exception {
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
+
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
         dispatchTaskRemoved(10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest), any());
-        mInOrder.verifyNoMoreInteractions();
         assertThat(gameSession10.mIsDestroyed).isTrue();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void gameTaskRemoved_removesTaskOverlay() throws Exception {
+        mGameServiceProviderInstance.start();
+
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        stopTask(10);
+
+        verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10));
+        verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10));
+        verifyNoMoreInteractions(mMockWindowManagerInternal);
     }
 
     @Test
     public void gameTaskStartedAndSessionRequested_multipleTimes_createsMultipleGameSessions()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest10 =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest10);
-
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verifyNoMoreInteractions();
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
         assertThat(gameSession10.mIsDestroyed).isFalse();
         assertThat(gameSession11.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
     }
 
     @Test
-    public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSessions()
+    public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSession()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
-        // The game task is started twice, but a session is requested only for the second one.
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        startTask(11, GAME_A_MAIN_ACTIVITY);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verifyNoMoreInteractions();
-        assertThat(gameSession11.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
-        assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        assertThat(gameSession10.mIsDestroyed).isFalse();
+        assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).hasSize(1);
     }
 
     @Test
-    public void gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession()
+    public void gameTaskRemoved_multipleSessions_destroysOnlyThatGameSession()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest10 =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest10);
-
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
 
         dispatchTaskRemoved(10);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verifyNoMoreInteractions();
         assertThat(gameSession10.mIsDestroyed).isTrue();
         assertThat(gameSession11.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
     }
 
     @Test
-    public void allGameTasksRemoved_destroysAllGameSessions() throws Exception {
-        CreateGameSessionRequest createGameSessionRequest10 =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest10);
-
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
+    public void allGameTasksRemoved_destroysAllGameSessionsAndGameSessionServiceIsDisconnected() {
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
 
         dispatchTaskRemoved(10);
         dispatchTaskRemoved(11);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verifyNoMoreInteractions();
         assertThat(gameSession10.mIsDestroyed).isTrue();
         assertThat(gameSession11.mIsDestroyed).isTrue();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
     }
 
     @Test
-    public void gameTasksCreatedAndSessionsReq_afterAllPreviousSessionsDestroyed_createsSession()
+    public void createSessionRequested_afterAllPreviousSessionsDestroyed_createsSession()
             throws Exception {
-        CreateGameSessionRequest createGameSessionRequest10 =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest10);
-
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
-        CreateGameSessionRequest createGameSessionRequest12 =
-                new CreateGameSessionRequest(12, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> unusedGameSession12Future =
-                captureCreateGameSessionFuture(createGameSessionRequest12);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
 
         dispatchTaskRemoved(10);
         dispatchTaskRemoved(11);
 
-        dispatchTaskCreatedAndTriggerSessionRequest(12, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession12 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession12);
+        startTask(12, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(12);
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(12, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any());
-        mInOrder.verifyNoMoreInteractions();
+        FakeGameSession gameSession12 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage12 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(12)
+                .complete(new CreateGameSessionResult(gameSession12, mockSurfacePackage12));
+
         assertThat(gameSession10.mIsDestroyed).isTrue();
         assertThat(gameSession11.mIsDestroyed).isTrue();
         assertThat(gameSession12.mIsDestroyed).isFalse();
-        assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isTrue();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(2);
     }
 
     @Test
     public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception {
-        CreateGameSessionRequest createGameSessionRequest10 =
-                new CreateGameSessionRequest(10, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession10Future =
-                captureCreateGameSessionFuture(createGameSessionRequest10);
-
-        CreateGameSessionRequest createGameSessionRequest11 =
-                new CreateGameSessionRequest(11, GAME_A_PACKAGE);
-        Supplier<AndroidFuture<IBinder>> gameSession11Future =
-                captureCreateGameSessionFuture(createGameSessionRequest11);
-
         mGameServiceProviderInstance.start();
-        ArgumentCaptor<IGameServiceController> controllerArgumentCaptor = ArgumentCaptor.forClass(
-                IGameServiceController.class);
-        verify(mMockGameService).connected(controllerArgumentCaptor.capture());
-        dispatchTaskCreatedAndTriggerSessionRequest(10, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession10 = new IGameSessionStub();
-        gameSession10Future.get().complete(gameSession10);
-        dispatchTaskCreatedAndTriggerSessionRequest(11, GAME_A_MAIN_ACTIVITY,
-                controllerArgumentCaptor.getValue());
-        IGameSessionStub gameSession11 = new IGameSessionStub();
-        gameSession11Future.get().complete(gameSession11);
+
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        FakeGameSession gameSession10 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(10)
+                .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10));
+
+        startTask(11, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(11);
+
+        FakeGameSession gameSession11 = new FakeGameSession();
+        SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class);
+        mFakeGameSessionService.removePendingFutureForTaskId(11)
+                .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11));
+
         mGameServiceProviderInstance.stop();
 
-        mInOrder.verify(mMockGameService).connected(any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(10, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any());
-        mInOrder.verify(mMockGameService).gameStarted(
-                eq(new GameStartedEvent(11, GAME_A_PACKAGE)));
-        mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any());
-        mInOrder.verify(mMockGameService).disconnected();
-        mInOrder.verifyNoMoreInteractions();
         assertThat(gameSession10.mIsDestroyed).isTrue();
         assertThat(gameSession11.mIsDestroyed).isTrue();
         assertThat(mFakeGameServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1);
         assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse();
-        assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(1);
     }
 
-    private Supplier<AndroidFuture<IBinder>> captureCreateGameSessionFuture(
-            CreateGameSessionRequest expectedCreateGameSessionRequest) throws Exception {
-        final AtomicReference<AndroidFuture<IBinder>> gameSessionFuture = new AtomicReference<>();
-        doAnswer(invocation -> {
-            gameSessionFuture.set(invocation.getArgument(1));
-            return null;
-        }).when(mMockGameSessionService).create(eq(expectedCreateGameSessionRequest), any());
+    @Test
+    public void takeScreenshot_failureNoBitmapCaptured() throws Exception {
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
 
-        return gameSessionFuture::get;
+        IGameSessionController gameSessionController = getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
+        AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
+        gameSessionController.takeScreenshot(10, resultFuture);
+
+        GameScreenshotResult result = resultFuture.get();
+        assertEquals(GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR,
+                result.getStatus());
+        verify(mMockWindowManagerService).captureTaskBitmap(10);
     }
 
+    @Test
+    public void takeScreenshot_success() throws Exception {
+        when(mMockWindowManagerService.captureTaskBitmap(10)).thenReturn(TEST_BITMAP);
+
+        mGameServiceProviderInstance.start();
+        startTask(10, GAME_A_MAIN_ACTIVITY);
+        mFakeGameService.requestCreateGameSession(10);
+
+        IGameSessionController gameSessionController = getOnlyElement(
+                mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController;
+        AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>();
+        gameSessionController.takeScreenshot(10, resultFuture);
+
+        GameScreenshotResult result = resultFuture.get();
+        assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus());
+        assertEquals(TEST_BITMAP, result.getBitmap());
+    }
+
+    private void startTask(int taskId, ComponentName componentName) {
+        RunningTaskInfo runningTaskInfo = new RunningTaskInfo();
+        runningTaskInfo.taskId = taskId;
+        runningTaskInfo.displayId = 1;
+        runningTaskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 500, 800));
+        mRunningTaskInfos.add(runningTaskInfo);
+
+        dispatchTaskCreated(taskId, componentName);
+    }
+
+    private void stopTask(int taskId) {
+        mRunningTaskInfos.removeIf(runningTaskInfo -> runningTaskInfo.taskId == taskId);
+        dispatchTaskRemoved(taskId);
+    }
+
+
     private void dispatchTaskRemoved(int taskId) {
         dispatchTaskChangeEvent(taskStackListener -> {
             taskStackListener.onTaskRemoved(taskId);
         });
     }
 
-    private void dispatchTaskCreatedAndTriggerSessionRequest(int taskId,
-            @Nullable ComponentName componentName, IGameServiceController gameServiceController)
-            throws Exception {
-        dispatchTaskCreated(taskId, componentName);
-        gameServiceController.createGameSession(taskId);
-    }
-
     private void dispatchTaskCreated(int taskId, @Nullable ComponentName componentName) {
         dispatchTaskChangeEvent(taskStackListener -> {
             taskStackListener.onTaskCreated(taskId, componentName);
         });
     }
 
+    private void dispatchTaskFocused(int taskId, boolean focused) {
+        dispatchTaskChangeEvent(taskStackListener -> {
+            taskStackListener.onTaskFocusChanged(taskId, focused);
+        });
+    }
+
     private void dispatchTaskChangeEvent(
             ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) {
         for (ITaskStackListener taskStackListener : mTaskStackListeners) {
@@ -721,12 +701,129 @@
         }
     }
 
-    private static class IGameSessionStub extends IGameSession.Stub {
-        boolean mIsDestroyed = false;
+    static final class FakeGameService extends IGameService.Stub {
+        private IGameServiceController mGameServiceController;
+
+        public enum GameServiceState {
+            DISCONNECTED,
+            CONNECTED,
+        }
+
+        private ArrayList<GameStartedEvent> mGameStartedEvents = new ArrayList<>();
+        private int mConnectedCount = 0;
+        private GameServiceState mGameServiceState = GameServiceState.DISCONNECTED;
+
+        public GameServiceState getState() {
+            return mGameServiceState;
+        }
+
+        public int getConnectedCount() {
+            return mConnectedCount;
+        }
+
+        public ArrayList<GameStartedEvent> getGameStartedEvents() {
+            return mGameStartedEvents;
+        }
 
         @Override
-        public void destroy() {
-            mIsDestroyed = true;
+        public void connected(IGameServiceController gameServiceController) {
+            Preconditions.checkState(mGameServiceState == GameServiceState.DISCONNECTED);
+
+            mGameServiceState = GameServiceState.CONNECTED;
+            mConnectedCount += 1;
+            mGameServiceController = gameServiceController;
+        }
+
+        @Override
+        public void disconnected() {
+            Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED);
+
+            mGameServiceState = GameServiceState.DISCONNECTED;
+            mGameServiceController = null;
+        }
+
+        @Override
+        public void gameStarted(GameStartedEvent gameStartedEvent) {
+            Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED);
+
+            mGameStartedEvents.add(gameStartedEvent);
+        }
+
+        public void requestCreateGameSession(int task) {
+            Preconditions.checkState(mGameServiceState == GameServiceState.CONNECTED);
+
+            try {
+                mGameServiceController.createGameSession(task);
+            } catch (RemoteException ex) {
+                throw new AssertionError(ex);
+            }
         }
     }
-}
+
+    static final class FakeGameSessionService extends IGameSessionService.Stub {
+
+        private final ArrayList<CapturedCreateInvocation> mCapturedCreateInvocations =
+                new ArrayList<>();
+        private final HashMap<Integer, AndroidFuture<CreateGameSessionResult>>
+                mPendingCreateGameSessionResultFutures =
+                new HashMap<>();
+
+        public static final class CapturedCreateInvocation {
+            private final IGameSessionController mGameSessionController;
+            private final CreateGameSessionRequest mCreateGameSessionRequest;
+            private final GameSessionViewHostConfiguration mGameSessionViewHostConfiguration;
+
+            CapturedCreateInvocation(
+                    IGameSessionController gameSessionController,
+                    CreateGameSessionRequest createGameSessionRequest,
+                    GameSessionViewHostConfiguration gameSessionViewHostConfiguration) {
+                mGameSessionController = gameSessionController;
+                mCreateGameSessionRequest = createGameSessionRequest;
+                mGameSessionViewHostConfiguration = gameSessionViewHostConfiguration;
+            }
+        }
+
+        public ArrayList<CapturedCreateInvocation> getCapturedCreateInvocations() {
+            return mCapturedCreateInvocations;
+        }
+
+        public AndroidFuture<CreateGameSessionResult> removePendingFutureForTaskId(int taskId) {
+            return mPendingCreateGameSessionResultFutures.remove(taskId);
+        }
+
+        @Override
+        public void create(
+                IGameSessionController gameSessionController,
+                CreateGameSessionRequest createGameSessionRequest,
+                GameSessionViewHostConfiguration gameSessionViewHostConfiguration,
+                AndroidFuture createGameSessionResultFuture) {
+
+            mCapturedCreateInvocations.add(
+                    new CapturedCreateInvocation(
+                            gameSessionController,
+                            createGameSessionRequest,
+                            gameSessionViewHostConfiguration));
+
+            Preconditions.checkState(!mPendingCreateGameSessionResultFutures.containsKey(
+                    createGameSessionRequest.getTaskId()));
+            mPendingCreateGameSessionResultFutures.put(
+                    createGameSessionRequest.getTaskId(),
+                    createGameSessionResultFuture);
+        }
+    }
+
+    private static class FakeGameSession extends IGameSession.Stub {
+        boolean mIsDestroyed = false;
+        boolean mIsFocused = false;
+
+        @Override
+        public void onDestroyed() {
+            mIsDestroyed = true;
+        }
+
+        @Override
+        public void onTaskFocusChanged(boolean focused) {
+            mIsFocused = focused;
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index d741459..8a954ca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -383,6 +383,10 @@
 
         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
+        verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
+                .setWindow(
+                        anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
+                        anyLong(), eq(TAG_PREFETCH), any(), any());
         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
     }
 
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 c2e0a04..1e0f30e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -216,6 +216,7 @@
         val displayMetrics: DisplayMetrics = mock()
         val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
         val handler = TestHandler(null)
+        val defaultAppProvider: DefaultAppProvider = mock()
     }
 
     companion object {
@@ -294,6 +295,7 @@
         whenever(mocks.injector.domainVerificationManagerInternal)
             .thenReturn(mocks.domainVerificationManagerInternal)
         whenever(mocks.injector.handler) { mocks.handler }
+        whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider }
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
         whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
@@ -519,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/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
new file mode 100644
index 0000000..fe7e2d9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -0,0 +1,507 @@
+/*
+ * 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.pm
+
+import android.content.Intent
+import android.content.pm.PackageManagerInternal
+import android.content.pm.SuspendDialogInfo
+import android.os.Binder
+import android.os.Build
+import android.os.Bundle
+import android.os.PersistableBundle
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.ArrayMap
+import android.util.SparseArray
+import com.android.server.pm.pkg.PackageStateInternal
+import com.android.server.testutils.TestHandler
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class SuspendPackageHelperTest {
+
+    companion object {
+        const val TEST_PACKAGE_1 = "com.android.test.package1"
+        const val TEST_PACKAGE_2 = "com.android.test.package2"
+        const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
+        const val NONEXISTENT_PACKAGE = "com.android.test.nonexistent"
+        const val DEVICE_ADMIN_PACKAGE = "com.android.test.known.device.admin"
+        const val DEFAULT_HOME_PACKAGE = "com.android.test.known.home"
+        const val DIALER_PACKAGE = "com.android.test.known.dialer"
+        const val INSTALLER_PACKAGE = "com.android.test.known.installer"
+        const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
+        const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
+        const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
+        const val TEST_USER_ID = 0
+    }
+
+    lateinit var pms: PackageManagerService
+    lateinit var suspendPackageHelper: SuspendPackageHelper
+    lateinit var testHandler: TestHandler
+    lateinit var defaultAppProvider: DefaultAppProvider
+    lateinit var packageSetting1: PackageStateInternal
+    lateinit var packageSetting2: PackageStateInternal
+    lateinit var ownerSetting: PackageStateInternal
+    lateinit var packagesToSuspend: Array<String>
+    lateinit var uidsToSuspend: IntArray
+
+    @Mock
+    lateinit var broadcastHelper: BroadcastHelper
+    @Mock
+    lateinit var protectedPackages: ProtectedPackages
+
+    @Captor
+    lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+
+    @Rule
+    @JvmField
+    val rule = MockSystemRule()
+    var deviceOwnerUid = 0
+
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        rule.system().stageNominalSystemState()
+        pms = spy(createPackageManagerService(
+            TEST_PACKAGE_1, TEST_PACKAGE_2, DEVICE_OWNER_PACKAGE, DEVICE_ADMIN_PACKAGE,
+            DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE,
+            VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE))
+        suspendPackageHelper = SuspendPackageHelper(
+            pms, rule.mocks().injector, broadcastHelper, protectedPackages)
+        defaultAppProvider = rule.mocks().defaultAppProvider
+        testHandler = rule.mocks().handler
+        packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!!
+        packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!!
+        ownerSetting = pms.getPackageStateInternal(DEVICE_OWNER_PACKAGE)!!
+        deviceOwnerUid = UserHandle.getUid(TEST_USER_ID, ownerSetting.appId)
+        packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
+
+        whenever(protectedPackages.getDeviceOwnerOrProfileOwnerPackage(eq(TEST_USER_ID)))
+            .thenReturn(DEVICE_OWNER_PACKAGE)
+        whenever(rule.mocks().userManagerService.hasUserRestriction(
+            eq(UserManager.DISALLOW_APPS_CONTROL), eq(TEST_USER_ID))).thenReturn(true)
+        whenever(rule.mocks().userManagerService.hasUserRestriction(
+            eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
+        mockKnownPackages(pms)
+    }
+
+    @Test
+    fun setPackagesSuspended() {
+        val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+
+        verify(pms).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
+            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+            nullable(), nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
+            nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
+            nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
+
+        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        assertThat(failedNames).isEmpty()
+    }
+
+    @Test
+    fun setPackagesSuspended_emptyPackageName() {
+        var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */,
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+
+        assertThat(failedNames).isNull()
+
+        failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+
+        assertThat(failedNames).isEmpty()
+    }
+
+    @Test
+    fun setPackagesSuspended_callerIsNotAllowed() {
+        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid())
+
+        assertThat(failedNames).asList().hasSize(1)
+        assertThat(failedNames).asList().contains(TEST_PACKAGE_2)
+    }
+
+    @Test
+    fun setPackagesSuspended_callerSuspendItself() {
+        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+
+        assertThat(failedNames).asList().hasSize(1)
+        assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE)
+    }
+
+    @Test
+    fun setPackagesSuspended_nonexistentPackage() {
+        val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+
+        assertThat(failedNames).asList().hasSize(1)
+        assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE)
+    }
+
+    @Test
+    fun setPackagesSuspended_knownPackages() {
+        val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
+            INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
+        val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages,
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
+
+        assertThat(failedNames.size).isEqualTo(knownPackages.size)
+        for (pkg in knownPackages) {
+            assertThat(failedNames).asList().contains(pkg)
+        }
+    }
+
+    @Test
+    fun setPackagesUnsuspended() {
+        val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+        failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
+            false /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+
+        verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
+            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+            nullable(), nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
+            nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
+            nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
+            nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
+            nullable(), nullable())
+
+        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        assertThat(failedNames).isEmpty()
+    }
+
+    @Test
+    fun getUnsuspendablePackagesForUser() {
+        val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
+            INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
+        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
+            suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid)
+
+        assertThat(results.size).isEqualTo(unsuspendables.size)
+        for (pkg in unsuspendables) {
+            assertThat(results).asList().contains(pkg)
+        }
+    }
+
+    @Test
+    fun getUnsuspendablePackagesForUser_callerIsNotAllowed() {
+        val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        val results = suspendPackageHelper.getUnsuspendablePackagesForUser(
+            suspendables, TEST_USER_ID, Binder.getCallingUid())
+
+        assertThat(results.size).isEqualTo(suspendables.size)
+        for (pkg in suspendables) {
+            assertThat(results).asList().contains(pkg)
+        }
+    }
+
+    @Test
+    fun getSuspendedPackageAppExtras() {
+        val appExtras = PersistableBundle()
+        appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
+            true /* suspended */, appExtras, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+
+        val result = suspendPackageHelper.getSuspendedPackageAppExtras(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!!
+
+        assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1)
+    }
+
+    @Test
+    fun removeSuspensionsBySuspendingPackage() {
+        val appExtras = PersistableBundle()
+        appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages,
+            true /* suspended */, appExtras, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+        assertThat(suspendPackageHelper.getSuspendingPackage(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+        assertThat(suspendPackageHelper.getSuspendingPackage(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
+        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
+
+        suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages,
+            { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID)
+
+        testHandler.flush()
+        verify(pms, times(2)).scheduleWritePackageRestrictionsLocked(eq(TEST_USER_ID))
+        verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
+            nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+            nullable(), nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
+            nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
+            nullable(), nullable())
+        verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
+            nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
+            nullable(), nullable())
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
+        assertThat(suspendPackageHelper.getSuspendingPackage(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
+        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
+        assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
+    }
+
+    @Test
+    fun getSuspendedPackageLauncherExtras() {
+        val launcherExtras = PersistableBundle()
+        launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
+            true /* suspended */, null /* appExtras */, launcherExtras,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+
+        val result = suspendPackageHelper.getSuspendedPackageLauncherExtras(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)!!
+
+        assertThat(result.getString(TEST_PACKAGE_2)).isEqualTo(TEST_PACKAGE_2)
+    }
+
+    @Test
+    fun isPackageSuspended() {
+        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+
+        assertThat(suspendPackageHelper.isPackageSuspended(
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isTrue()
+    }
+
+    @Test
+    fun getSuspendingPackage() {
+        val launcherExtras = PersistableBundle()
+        launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
+        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2),
+            true /* suspended */, null /* appExtras */, launcherExtras,
+            null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+    }
+
+    @Test
+    fun getSuspendedDialogInfo() {
+        val dialogInfo = SuspendDialogInfo.Builder()
+            .setTitle(TEST_PACKAGE_1).build()
+        var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1),
+            true /* suspended */, null /* appExtras */, null /* launcherExtras */,
+            dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)
+        testHandler.flush()
+        assertThat(failedNames).isEmpty()
+
+        val result = suspendPackageHelper.getSuspendedDialogInfo(
+            TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
+
+        assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
+
+        suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+        testHandler.flush()
+        verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+        assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        assertThat(changedUids).asList().containsExactly(
+                packageSetting1.appId, packageSetting2.appId)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
+
+        suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+        testHandler.flush()
+        verify(broadcastHelper, times(2)).sendPackageBroadcast(
+                any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+                nullable(), any(), nullable())
+
+        bundleCaptor.allValues.forEach {
+            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+            assertThat(changedPackages?.size).isEqualTo(1)
+            assertThat(changedUids?.size).isEqualTo(1)
+            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+        }
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
+        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+        mockAllowList(packageSetting2, null)
+
+        suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+        testHandler.flush()
+        verify(broadcastHelper, times(2)).sendPackageBroadcast(
+                any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
+                nullable(), nullable(), nullable())
+
+        bundleCaptor.allValues.forEach {
+            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+            assertThat(changedPackages?.size).isEqualTo(1)
+            assertThat(changedUids?.size).isEqualTo(1)
+            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+        }
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun sendPackagesSuspendModifiedForUser() {
+        suspendPackageHelper.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+        testHandler.flush()
+        verify(broadcastHelper).sendPackageBroadcast(
+                eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
+                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+        var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+        assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+        assertThat(modifiedUids).asList().containsExactly(
+                packageSetting1.appId, packageSetting2.appId)
+    }
+
+    private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
+        this.put(TEST_USER_ID, uids)
+    }
+
+    private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
+        whenever(rule.mocks().appsFilter.getVisibilityAllowList(
+            argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
+            any() as ArrayMap<String, out PackageStateInternal>
+        ))
+            .thenReturn(list)
+    }
+
+    private fun mockKnownPackages(pms: PackageManagerService) {
+        Mockito.doAnswer { it.arguments[0] == DEVICE_ADMIN_PACKAGE }.`when`(pms)
+            .isPackageDeviceAdmin(any(), any())
+        Mockito.doReturn(DEFAULT_HOME_PACKAGE).`when`(defaultAppProvider)
+            .getDefaultHome(eq(TEST_USER_ID))
+        Mockito.doReturn(DIALER_PACKAGE).`when`(defaultAppProvider)
+            .getDefaultDialer(eq(TEST_USER_ID))
+        Mockito.doReturn(arrayOf(INSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+            eq(PackageManagerInternal.PACKAGE_INSTALLER), eq(TEST_USER_ID))
+        Mockito.doReturn(arrayOf(UNINSTALLER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+            eq(PackageManagerInternal.PACKAGE_UNINSTALLER), eq(TEST_USER_ID))
+        Mockito.doReturn(arrayOf(VERIFIER_PACKAGE)).`when`(pms).getKnownPackageNamesInternal(
+            eq(PackageManagerInternal.PACKAGE_VERIFIER), eq(TEST_USER_ID))
+        Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
+            .getKnownPackageNamesInternal(
+                eq(PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
+    }
+
+    private fun createPackageManagerService(vararg stageExistingPackages: String):
+            PackageManagerService {
+        stageExistingPackages.forEach {
+            rule.system().stageScanExistingPackage(it, 1L,
+                    rule.system().dataAppDirectory)
+        }
+        var pms = PackageManagerService(rule.mocks().injector,
+                false /*coreOnly*/,
+                false /*factoryTest*/,
+                MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+                false /*isEngBuild*/,
+                false /*isUserDebugBuild*/,
+                Build.VERSION_CODES.CUR_DEVELOPMENT,
+                Build.VERSION.INCREMENTAL,
+                false /*snapshotEnabled*/)
+        rule.system().validateFinalState()
+        return pms
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
deleted file mode 100644
index 02ee35b..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
+++ /dev/null
@@ -1,184 +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.server.pm
-
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.util.ArrayMap
-import android.util.SparseArray
-import com.android.server.pm.pkg.PackageStateInternal
-import com.android.server.testutils.any
-import com.android.server.testutils.eq
-import com.android.server.testutils.nullable
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Captor
-import org.mockito.Mockito.argThat
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(JUnit4::class)
-class SuspendPackagesBroadcastTest {
-
-    companion object {
-        const val TEST_PACKAGE_1 = "com.android.test.package1"
-        const val TEST_PACKAGE_2 = "com.android.test.package2"
-        const val TEST_USER_ID = 0
-    }
-
-    lateinit var pms: PackageManagerService
-    lateinit var packageSetting1: PackageStateInternal
-    lateinit var packageSetting2: PackageStateInternal
-    lateinit var packagesToSuspend: Array<String>
-    lateinit var uidsToSuspend: IntArray
-
-    @Captor
-    lateinit var bundleCaptor: ArgumentCaptor<Bundle>
-
-    @Rule
-    @JvmField
-    val rule = MockSystemRule()
-
-    @Before
-    @Throws(Exception::class)
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        rule.system().stageNominalSystemState()
-        pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
-        packageSetting1 = pms.getPackageStateInternal(TEST_PACKAGE_1)!!
-        packageSetting2 = pms.getPackageStateInternal(TEST_PACKAGE_2)!!
-        packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
-        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
-        mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
-
-        pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
-                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
-        verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
-
-        var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-        assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        assertThat(changedUids).asList().containsExactly(
-                packageSetting1.appId, packageSetting2.appId)
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
-        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
-        mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
-
-        pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
-                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
-        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
-
-        bundleCaptor.allValues.forEach {
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
-        }
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
-        mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
-        mockAllowList(packageSetting2, null)
-
-        pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
-                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
-        verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
-        bundleCaptor.allValues.forEach {
-            var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-            var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-            assertThat(changedPackages?.size).isEqualTo(1)
-            assertThat(changedUids?.size).isEqualTo(1)
-            assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
-            assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
-        }
-    }
-
-    @Test
-    @Throws(Exception::class)
-    fun sendPackagesSuspendModifiedForUser() {
-        pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
-                packagesToSuspend, uidsToSuspend, TEST_USER_ID)
-        verify(pms).sendPackageBroadcast(
-                eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
-                anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
-        var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
-        var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
-        assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
-        assertThat(modifiedUids).asList().containsExactly(
-                packageSetting1.appId, packageSetting2.appId)
-    }
-
-    private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
-        this.put(TEST_USER_ID, uids)
-    }
-
-    private fun mockAllowList(pkgSetting: PackageStateInternal, list: SparseArray<IntArray>?) {
-        whenever(rule.mocks().appsFilter.getVisibilityAllowList(
-            argThat { it?.packageName == pkgSetting.packageName }, any(IntArray::class.java),
-            any() as ArrayMap<String, out PackageStateInternal>
-        ))
-            .thenReturn(list)
-    }
-
-    private fun createPackageManagerService(vararg stageExistingPackages: String):
-            PackageManagerService {
-        stageExistingPackages.forEach {
-            rule.system().stageScanExistingPackage(it, 1L,
-                    rule.system().dataAppDirectory)
-        }
-        var pms = PackageManagerService(rule.mocks().injector,
-                false /*coreOnly*/,
-                false /*factoryTest*/,
-                MockSystem.DEFAULT_VERSION_INFO.fingerprint,
-                false /*isEngBuild*/,
-                false /*isUserDebugBuild*/,
-                Build.VERSION_CODES.CUR_DEVELOPMENT,
-                Build.VERSION.INCREMENTAL,
-                false /*snapshotEnabled*/)
-        rule.system().validateFinalState()
-        return pms
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 9666758..0b488b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -153,6 +153,9 @@
         sContext.getTestablePermissions().setPermission(
                 android.Manifest.permission.SET_WALLPAPER,
                 PackageManager.PERMISSION_GRANTED);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT,
+                PackageManager.PERMISSION_GRANTED);
         doNothing().when(sContext).sendBroadcastAsUser(any(), any());
 
         //Wallpaper components
@@ -433,6 +436,15 @@
         assertTrue(timestamps[1] > timestamps[0]);
     }
 
+    @Test
+    public void testSetWallpaperDimAmount() throws RemoteException {
+        mService.switchUser(USER_SYSTEM, null);
+        float dimAmount = 0.7f;
+        mService.setWallpaperDimAmount(dimAmount);
+        assertEquals("Getting dim amount should match after setting the dim amount",
+                mService.getWallpaperDimAmount(), dimAmount, 0.0);
+    }
+
     // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
     // non-current user must not bind to wallpaper service.
     private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 36c37c4..677f0f6 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -541,11 +541,14 @@
                     | ActivityManager.UID_OBSERVER_CAPABILITY
         };
         final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length];
+        doReturn(Process.myUid()).when(sPackageManagerInternal)
+                .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId());
         for (int i = 0; i < observers.length; ++i) {
             observers[i] = mock(IUidObserver.Stub.class);
             when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
             mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
-                    ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
+                    ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */,
+                    mContext.getOpPackageName());
 
             // When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
             // mock in RemoteCallbackList class. We don't want to test those interactions and
@@ -674,10 +677,12 @@
         mockNoteOperation();
 
         final IUidObserver observer = mock(IUidObserver.Stub.class);
-
         when(observer.asBinder()).thenReturn((IBinder) observer);
+        doReturn(Process.myUid()).when(sPackageManagerInternal)
+                .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId());
         mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
-                ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
+                ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */,
+                mContext.getOpPackageName());
         // When we invoke AMS.registerUidObserver, there are some interactions with observer
         // mock in RemoteCallbackList class. We don't want to test those interactions and
         // at the same time, we don't want those to interfere with verifyNoMoreInteractions.
@@ -771,7 +776,9 @@
 
         final IUidObserver observer = mock(IUidObserver.Stub.class);
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        mAms.registerUidObserver(observer, 0, 0, null);
+        doReturn(Process.myUid()).when(sPackageManagerInternal)
+                .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId());
+        mAms.registerUidObserver(observer, 0, 0, mContext.getOpPackageName());
         // Verify that when observers are registered, then validateUids is correctly updated.
         addPendingUidChanges(pendingItemsForUids);
         mAms.mUidObserverController.dispatchUidsChanged();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
index a06a782..fc55a9f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java
@@ -52,7 +52,7 @@
     @Mock
     private ClientMonitorCallbackConverter mClientCallback;
     @Mock
-    private BaseClientMonitor.Callback mSchedulerCallback;
+    private ClientMonitorCallback mSchedulerCallback;
 
     @Before
     public void setUp() {
@@ -96,7 +96,7 @@
         }
 
         @Override
-        public void start(@NonNull Callback callback) {
+        public void start(@NonNull ClientMonitorCallback callback) {
             super.start(callback);
             startHalOperation();
         }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
new file mode 100644
index 0000000..51d234d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.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.server.biometrics.sensors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class BaseClientMonitorTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private IBinder mToken;
+    private @Mock ClientMonitorCallbackConverter mListener;
+    @Mock
+    private BiometricLogger mLogger;
+    @Mock
+    private ClientMonitorCallback mCallback;
+
+    private TestClientMonitor mClientMonitor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mClientMonitor = new TestClientMonitor();
+    }
+
+    @Test
+    public void preparesForDeath() throws RemoteException {
+        verify(mToken).linkToDeath(eq(mClientMonitor), anyInt());
+
+        mClientMonitor.binderDied();
+
+        assertThat(mClientMonitor.mCanceled).isTrue();
+        assertThat(mClientMonitor.getListener()).isNull();
+    }
+
+    @Test
+    public void ignoresDeathWhenDone() {
+        mClientMonitor.markAlreadyDone();
+        mClientMonitor.binderDied();
+
+        assertThat(mClientMonitor.mCanceled).isFalse();
+    }
+
+    @Test
+    public void start() {
+        mClientMonitor.start(mCallback);
+
+        verify(mCallback).onClientStarted(eq(mClientMonitor));
+    }
+
+    @Test
+    public void destroy() {
+        mClientMonitor.destroy();
+        mClientMonitor.destroy();
+
+        assertThat(mClientMonitor.isAlreadyDone()).isTrue();
+        verify(mToken).unlinkToDeath(eq(mClientMonitor), anyInt());
+    }
+
+    @Test
+    public void hasRequestId() {
+        assertThat(mClientMonitor.hasRequestId()).isFalse();
+
+        final int id = 200;
+        mClientMonitor.setRequestId(id);
+        assertThat(mClientMonitor.hasRequestId()).isTrue();
+        assertThat(mClientMonitor.getRequestId()).isEqualTo(id);
+    }
+
+    private class TestClientMonitor extends BaseClientMonitor implements Interruptable {
+        public boolean mCanceled = false;
+
+        TestClientMonitor() {
+            super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */,
+                    5 /* sensorId */, mLogger);
+        }
+
+        @Override
+        public int getProtoEnum() {
+            return 0;
+        }
+
+        @Override
+        public void cancel() {
+            mCanceled = true;
+        }
+
+        @Override
+        public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) {
+            mCanceled = true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index d4bac2c..8751cf3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -61,11 +61,11 @@
     @Mock
     private InterruptableMonitor<FakeHal> mClientMonitor;
     @Mock
-    private BaseClientMonitor.Callback mClientCallback;
+    private ClientMonitorCallback mClientCallback;
     @Mock
     private FakeHal mHal;
     @Captor
-    ArgumentCaptor<BaseClientMonitor.Callback> mStartCallback;
+    ArgumentCaptor<ClientMonitorCallback> mStartCallback;
 
     private Handler mHandler;
     private BiometricSchedulerOperation mOperation;
@@ -89,7 +89,7 @@
         assertThat(mOperation.isFinished()).isFalse();
 
         final boolean started = mOperation.startWithCookie(
-                mock(BaseClientMonitor.Callback.class), cookie);
+                mock(ClientMonitorCallback.class), cookie);
 
         assertThat(started).isTrue();
         verify(mClientMonitor).start(mStartCallback.capture());
@@ -106,7 +106,7 @@
 
         assertThat(mOperation.isReadyToStart()).isEqualTo(goodCookie);
         final boolean started = mOperation.startWithCookie(
-                mock(BaseClientMonitor.Callback.class), badCookie);
+                mock(ClientMonitorCallback.class), badCookie);
 
         assertThat(started).isFalse();
         assertThat(mOperation.isStarted()).isFalse();
@@ -119,7 +119,7 @@
         when(mClientMonitor.getCookie()).thenReturn(0);
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback cb = mock(ClientMonitorCallback.class);
         mOperation.start(cb);
         verify(mClientMonitor).start(mStartCallback.capture());
         mStartCallback.getValue().onClientStarted(mClientMonitor);
@@ -146,7 +146,7 @@
         when(mClientMonitor.getCookie()).thenReturn(0);
         when(mClientMonitor.getFreshDaemon()).thenReturn(null);
 
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback cb = mock(ClientMonitorCallback.class);
         mOperation.start(cb);
         verify(mClientMonitor, never()).start(any());
 
@@ -164,17 +164,17 @@
     public void doesNotStartWithCookie() {
         when(mClientMonitor.getCookie()).thenReturn(9);
         assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+                () -> mOperation.start(mock(ClientMonitorCallback.class)));
     }
 
     @Test
     public void cannotRestart() {
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
+        mOperation.start(mock(ClientMonitorCallback.class));
 
         assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+                () -> mOperation.start(mock(ClientMonitorCallback.class)));
     }
 
     @Test
@@ -187,14 +187,14 @@
         verify(mClientMonitor).unableToStart();
         verify(mClientMonitor).destroy();
         assertThrows(IllegalStateException.class,
-                () -> mOperation.start(mock(BaseClientMonitor.Callback.class)));
+                () -> mOperation.start(mock(ClientMonitorCallback.class)));
     }
 
     @Test
     public void cannotAbortRunning() {
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
+        mOperation.start(mock(ClientMonitorCallback.class));
 
         assertThrows(IllegalStateException.class, () -> mOperation.abort());
     }
@@ -203,8 +203,8 @@
     public void cancel() {
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        final BaseClientMonitor.Callback startCb = mock(BaseClientMonitor.Callback.class);
-        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback startCb = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
         mOperation.start(startCb);
         verify(mClientMonitor).start(mStartCallback.capture());
         mStartCallback.getValue().onClientStarted(mClientMonitor);
@@ -230,12 +230,12 @@
     public void cancelWithoutStarting() {
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        final BaseClientMonitor.Callback cancelCb = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
         mOperation.cancel(mHandler, cancelCb);
 
         assertThat(mOperation.isCanceling()).isTrue();
-        ArgumentCaptor<BaseClientMonitor.Callback> cbCaptor =
-                ArgumentCaptor.forClass(BaseClientMonitor.Callback.class);
+        ArgumentCaptor<ClientMonitorCallback> cbCaptor =
+                ArgumentCaptor.forClass(ClientMonitorCallback.class);
         verify(mClientMonitor).cancelWithoutStarting(cbCaptor.capture());
 
         cbCaptor.getValue().onClientFinished(mClientMonitor, true);
@@ -278,7 +278,7 @@
         }
 
         mOperation.markCanceling();
-        final BaseClientMonitor.Callback cb = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback cb = mock(ClientMonitorCallback.class);
         if (withCookie != null) {
             mOperation.startWithCookie(cb, withCookie);
         } else {
@@ -307,12 +307,12 @@
     private void cancelWatchdog(boolean start) {
         when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
 
-        mOperation.start(mock(BaseClientMonitor.Callback.class));
+        mOperation.start(mock(ClientMonitorCallback.class));
         if (start) {
             verify(mClientMonitor).start(mStartCallback.capture());
             mStartCallback.getValue().onClientStarted(mClientMonitor);
         }
-        mOperation.cancel(mHandler, mock(BaseClientMonitor.Callback.class));
+        mOperation.cancel(mHandler, mock(ClientMonitorCallback.class));
 
         assertThat(mOperation.isCanceling()).isTrue();
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index ac08319..c99d656 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -114,13 +114,13 @@
         final TestHalClientMonitor client2 = new TestHalClientMonitor(
                 mContext, mToken, () -> mock(Object.class));
 
-        final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
-        final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
         mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
-                mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
+                mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
         assertEquals(1, mScheduler.mPendingOperations.size());
@@ -152,13 +152,13 @@
         final TestHalClientMonitor client2 =
                 new TestHalClientMonitor(mContext, mToken, () -> daemon2);
 
-        final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
-        final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
+        final ClientMonitorCallback callback2 = mock(ClientMonitorCallback.class);
 
         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
         // to pretend like there are two operations in the queue before kicking things off
         mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
-                mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
+                mock(BaseClientMonitor.class), mock(ClientMonitorCallback.class));
 
         mScheduler.scheduleClientMonitor(client1, callback1);
         assertEquals(1, mScheduler.mPendingOperations.size());
@@ -187,7 +187,7 @@
         final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class);
         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
-        final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+        final ClientMonitorCallback callback1 = mock(ClientMonitorCallback.class);
 
         // Schedule a BiometricPrompt authentication request
         mScheduler.scheduleClientMonitor(client1, callback1);
@@ -628,7 +628,7 @@
         }
 
         @Override
-        public void start(@NonNull Callback callback) {
+        public void start(@NonNull ClientMonitorCallback callback) {
             super.start(callback);
             assertFalse(mStarted);
             mStarted = true;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
index 09b5c5c..587bb60 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -17,36 +17,62 @@
 package com.android.server.biometrics.sensors;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
 
 @Presubmit
 @SmallTest
 public class CompositeCallbackTest {
 
+    @Mock
+    private BaseClientMonitor mClientMonitor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     @Test
-    public void testNullCallback() {
-        BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
-        BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
-        BaseClientMonitor.Callback callback3 = null;
+    public void testCallbacks() {
+        testCallbacks(mock(ClientMonitorCallback.class), mock(ClientMonitorCallback.class));
+    }
 
-        BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
-                callback1, callback2, callback3);
+    @Test
+    public void testNullCallbacks() {
+        testCallbacks(null, mock(ClientMonitorCallback.class),
+                null, mock(ClientMonitorCallback.class));
+    }
 
-        BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+    private void testCallbacks(ClientMonitorCallback... callbacks) {
+        final ClientMonitorCallback[] expected = Arrays.stream(callbacks)
+                .filter(Objects::nonNull).toArray(ClientMonitorCallback[]::new);
 
-        callback.onClientStarted(clientMonitor);
-        verify(callback1).onClientStarted(eq(clientMonitor));
-        verify(callback2).onClientStarted(eq(clientMonitor));
+        ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks);
 
-        callback.onClientFinished(clientMonitor, true /* success */);
-        verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
-        verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+        callback.onClientStarted(mClientMonitor);
+        final InOrder order = inOrder(expected);
+        for (ClientMonitorCallback cb : expected) {
+            order.verify(cb).onClientStarted(eq(mClientMonitor));
+        }
+
+        callback.onClientFinished(mClientMonitor, true /* success */);
+        Collections.reverse(Arrays.asList(expected));
+        for (ClientMonitorCallback cb : expected) {
+            order.verify(cb).onClientFinished(eq(mClientMonitor), eq(true));
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 407f5fb..a11709a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -155,7 +155,7 @@
         assertNull(mScheduler.mCurrentOperation);
 
         final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
-                mock(BaseClientMonitor.class), new BaseClientMonitor.Callback() {});
+                mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
         mScheduler.mCurrentOperation = fakeOperation;
         startUserClient.mCallback.onClientFinished(startUserClient, true);
         assertSame(fakeOperation, mScheduler.mCurrentOperation);
@@ -234,7 +234,7 @@
         }
 
         @Override
-        public void start(@NonNull Callback callback) {
+        public void start(@NonNull ClientMonitorCallback callback) {
             super.start(callback);
             onUserStopped();
         }
@@ -248,7 +248,7 @@
     private static class TestStartUserClient extends StartUserClient<Object, Object> {
         private final boolean mShouldFinish;
 
-        Callback mCallback;
+        ClientMonitorCallback mCallback;
 
         public TestStartUserClient(@NonNull Context context,
                 @NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
@@ -263,7 +263,7 @@
         }
 
         @Override
-        public void start(@NonNull Callback callback) {
+        public void start(@NonNull ClientMonitorCallback callback) {
             super.start(callback);
 
             mCallback = callback;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
index 55dc035..931fad1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
@@ -34,7 +34,7 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 
 import org.junit.Before;
@@ -61,7 +61,7 @@
     @Mock
     private IFaceServiceReceiver mOtherReceiver;
     @Mock
-    private BaseClientMonitor.Callback mMonitorCallback;
+    private ClientMonitorCallback mMonitorCallback;
 
     private FaceGenerateChallengeClient mClient;
 
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
new file mode 100644
index 0000000..ff1b6f6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.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.server.companion.virtual;
+
+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;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputControllerTest {
+
+    @Mock
+    private InputManagerInternal mInputManagerInternalMock;
+    @Mock
+    private InputController.NativeWrapper mNativeWrapperMock;
+
+    private InputController mInputController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+
+        mInputController = new InputController(new Object(), mNativeWrapperMock);
+    }
+
+    @Test
+    public void unregisterInputDevice_allMiceUnregistered_unsetValues() {
+        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
+    public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() {
+        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));
+        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 0a0f7d7..e36263e 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
@@ -18,42 +18,62 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
+import android.app.admin.DevicePolicyManager;
+import android.companion.AssociationInfo;
+import android.companion.virtual.IVirtualDeviceActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Point;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
 import android.hardware.input.VirtualMouseScrollEvent;
 import android.hardware.input.VirtualTouchEvent;
+import android.net.MacAddress;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.WorkSource;
 import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
 
 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;
 
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class VirtualDeviceManagerServiceTest {
 
     private static final String DEVICE_NAME = "device name";
@@ -73,6 +93,17 @@
     private DisplayManagerInternal mDisplayManagerInternalMock;
     @Mock
     private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
+    @Mock
+    private DevicePolicyManager mDevicePolicyManagerMock;
+    @Mock
+    private InputManagerInternal mInputManagerInternalMock;
+    @Mock
+    private IVirtualDeviceActivityListener mActivityListener;
+    @Mock
+    IPowerManager mIPowerManagerMock;
+    @Mock
+    IThermalService mIThermalServiceMock;
+    private PowerManager mPowerManager;
 
     @Before
     public void setUp() {
@@ -81,17 +112,105 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
+        doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+
         mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         doNothing().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mDevicePolicyManagerMock);
+
+        mPowerManager = new PowerManager(mContext, mIPowerManagerMock, mIThermalServiceMock,
+                new Handler(TestableLooper.get(this).getLooper()));
+        when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+
         mInputController = new InputController(new Object(), mNativeWrapperMock);
+        AssociationInfo associationInfo = new AssociationInfo(1, 0, null,
+                MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                /* association info */ null, new Binder(), /* uid */ 0, mInputController,
-                (int associationId) -> {}, mPendingTrampolineCallback,
+                associationInfo, new Binder(), /* uid */ 0, mInputController,
+                (int associationId) -> {
+                }, mPendingTrampolineCallback, mActivityListener,
                 new VirtualDeviceParams.Builder().build());
     }
 
     @Test
+    public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        // This call should not throw any exceptions.
+        mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
+    }
+
+    @Test
+    public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), anyInt());
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+    }
+
+    @Test
+    public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
+            throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        assertThrows(IllegalStateException.class,
+                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(displayId));
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+    }
+
+    @Test
+    public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
+        final int unknownDisplayId = 999;
+        assertThrows(IllegalStateException.class,
+                () -> mDeviceImpl.onVirtualDisplayRemovedLocked(unknownDisplayId));
+    }
+
+    @Test
+    public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
+                anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+
+        IBinder wakeLock = wakeLockCaptor.getValue();
+        mDeviceImpl.onVirtualDisplayRemovedLocked(displayId);
+        verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
+    }
+
+    @Test
+    public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
+        final int displayId = 2;
+        mDeviceImpl.onVirtualDisplayCreatedLocked(displayId);
+        ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
+        TestableLooper.get(this).processAllMessages();
+        verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(),
+                anyInt(),
+                nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+                nullable(String.class), eq(displayId));
+        IBinder wakeLock = wakeLockCaptor.getValue();
+
+        // Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
+        mDeviceImpl.close();
+        verify(mIPowerManagerMock, Mockito.times(1)).releaseWakeLock(eq(wakeLock), anyInt());
+    }
+
+    @Test
     public void createVirtualKeyboard_noDisplay_failsSecurityException() {
         assertThrows(
                 SecurityException.class,
@@ -154,7 +273,7 @@
         mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
-                .that(mInputController.mInputDeviceFds).isNotEmpty();
+                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
         verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
     }
 
@@ -164,7 +283,7 @@
         mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
                 BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches")
-                .that(mInputController.mInputDeviceFds).isNotEmpty();
+                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
         verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
     }
 
@@ -174,7 +293,7 @@
         mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
                 BINDER, new Point(WIDTH, HEIGHT));
         assertWithMessage("Virtual keyboard should register fd when the display matches")
-                .that(mInputController.mInputDeviceFds).isNotEmpty();
+                .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
                 WIDTH);
     }
@@ -194,7 +313,9 @@
         final int fd = 1;
         final int keyCode = KeyEvent.KEYCODE_A;
         final int action = VirtualKeyEvent.ACTION_UP;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1,
+                        /* displayId= */ 1));
         mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
                 .setAction(action).build());
         verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
@@ -217,7 +338,10 @@
         final int fd = 1;
         final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
                 .setButtonCode(buttonCode)
                 .setAction(action).build());
@@ -225,6 +349,22 @@
     }
 
     @Test
+    public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
+        final int fd = 1;
+        final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
+        final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        assertThrows(
+                IllegalStateException.class,
+                () ->
+                        mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
+                                .setButtonCode(buttonCode)
+                                .setAction(action).build()));
+    }
+
+    @Test
     public void sendRelativeEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
@@ -239,13 +379,32 @@
         final int fd = 1;
         final float x = -0.2f;
         final float y = 0.7f;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
                 .setRelativeX(x).setRelativeY(y).build());
         verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y);
     }
 
     @Test
+    public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
+        final int fd = 1;
+        final float x = -0.2f;
+        final float y = 0.7f;
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        assertThrows(
+                IllegalStateException.class,
+                () ->
+                        mDeviceImpl.sendRelativeEvent(BINDER,
+                                new VirtualMouseRelativeEvent.Builder()
+                                        .setRelativeX(x).setRelativeY(y).build()));
+    }
+
+    @Test
     public void sendScrollEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
@@ -261,7 +420,10 @@
         final int fd = 1;
         final float x = 0.5f;
         final float y = 1f;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        mInputController.mActivePointerDisplayId = 1;
         mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
                 .setXAxisMovement(x)
                 .setYAxisMovement(y).build());
@@ -269,6 +431,22 @@
     }
 
     @Test
+    public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() {
+        final int fd = 1;
+        final float x = 0.5f;
+        final float y = 1f;
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2,
+                        /* displayId= */ 1));
+        assertThrows(
+                IllegalStateException.class,
+                () ->
+                        mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
+                                .setXAxisMovement(x)
+                                .setYAxisMovement(y).build()));
+    }
+
+    @Test
     public void sendTouchEvent_noFd() {
         assertThrows(
                 IllegalArgumentException.class,
@@ -290,7 +468,9 @@
         final float x = 100.5f;
         final float y = 200.5f;
         final int action = VirtualTouchEvent.ACTION_UP;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
+                        /* displayId= */ 1));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
@@ -307,7 +487,9 @@
         final int action = VirtualTouchEvent.ACTION_UP;
         final float pressure = 1.0f;
         final float majorAxisSize = 10.0f;
-        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mInputController.mInputDeviceDescriptors.put(BINDER,
+                new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3,
+                        /* displayId= */ 1));
         mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
                 .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
                 .setPressure(pressure).setMajorAxisSize(majorAxisSize).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 1228d62..c0ad69f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -18,7 +18,6 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
-import static android.app.Notification.EXTRA_TEXT;
 import static android.app.Notification.EXTRA_TITLE;
 import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
@@ -34,12 +33,18 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR;
+import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192;
+import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP;
+import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN;
+import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 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;
@@ -90,7 +95,9 @@
 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;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -1112,6 +1119,10 @@
                 eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
                 eq(true), eq(UserHandle.SYSTEM));
 
+        verify(getServices().userManager, times(1)).setUserRestriction(
+                eq(UserManager.DISALLOW_ADD_CLONE_PROFILE),
+                eq(true), eq(UserHandle.SYSTEM));
+
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
@@ -1393,6 +1404,10 @@
                 eq(false),
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
 
+        verify(getServices().userManager)
+                .setUserRestriction(eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), eq(false),
+                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
                 eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(),
                 MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true));
@@ -4123,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);
@@ -4132,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();
@@ -7209,8 +7383,7 @@
         verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
         // Now the user should see a warning notification.
         verify(getServices().notificationManager, times(1))
-                .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE,
-                        EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT)));
+                .notify(anyInt(), any());
         // Apps shouldn't be suspended yet.
         verifyZeroInteractions(getServices().ipackageManager);
         clearInvocations(getServices().alarmManager);
@@ -7224,8 +7397,7 @@
         verifyZeroInteractions(getServices().alarmManager);
         // Now the user should see a notification about suspended apps.
         verify(getServices().notificationManager, times(1))
-                .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE,
-                        EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TEXT)));
+                .notify(anyInt(), any());
         // Verify that the apps are suspended.
         verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
                 any(), eq(true), any(), any(), any(), any(), anyInt());
@@ -7828,6 +8000,173 @@
                 () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM));
     }
 
+    @Test
+    public void testSetWifiMinimumSecurity_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+        assertThrows(SecurityException.class, () -> dpm.setMinimumRequiredWifiSecurityLevel(
+                DevicePolicyManager.WIFI_SECURITY_PERSONAL));
+    }
+
+    @Test
+    public void testSetWifiMinimumSecurity_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+
+        final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL,
+                WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192);
+        for (int level : allowedLevels) {
+            dpm.setMinimumRequiredWifiSecurityLevel(level);
+            assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level);
+        }
+    }
+
+    @Test
+    public void testSetWifiMinimumSecurity_asPoOfOrgOwnedDevice() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL,
+                WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192);
+        for (int level : allowedLevels) {
+            dpm.setMinimumRequiredWifiSecurityLevel(level);
+            assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level);
+        }
+    }
+
+    @Test
+    public void testSetSsidAllowlist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+        final Set<String> ssids = Collections.singleton("ssid1");
+        WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
+        assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
+    }
+
+    @Test
+    public void testSetSsidAllowlist_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+
+        final Set<String> ssids = Collections.singleton("ssid1");
+        WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
+        dpm.setWifiSsidPolicy(policy);
+        assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
+        assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
+                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST);
+    }
+
+    @Test
+    public void testSetSsidAllowlist_asPoOfOrgOwnedDevice() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
+        WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
+        dpm.setWifiSsidPolicy(policy);
+        assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
+        assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
+                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST);
+    }
+
+    @Test
+    public void testSetSsidAllowlist_emptyList() throws Exception {
+        setDeviceOwner();
+
+        final Set<String> ssids = new ArraySet<>();
+        assertThrows(IllegalArgumentException.class,
+                () -> WifiSsidPolicy.createAllowlistPolicy(ssids));
+    }
+
+    @Test
+    public void testSetSsidDenylist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
+        final Set<String> ssids = Collections.singleton("ssid1");
+        WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
+        assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
+    }
+
+    @Test
+    public void testSetSsidDenylist_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+
+        final Set<String> ssids = Collections.singleton("ssid1");
+        WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
+        dpm.setWifiSsidPolicy(policy);
+        assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
+        assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
+                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST);
+    }
+
+    @Test
+    public void testSetSsidDenylist_asPoOfOrgOwnedDevice() throws Exception {
+        final int managedProfileUserId = 15;
+        final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
+        addManagedProfile(admin1, managedProfileAdminUid, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
+        mContext.binder.callingUid = managedProfileAdminUid;
+
+        final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
+        WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
+        dpm.setWifiSsidPolicy(policy);
+        assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
+        assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
+                WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST);
+    }
+
+    @Test
+    public void testSetSsidDenylist_emptyList() throws Exception {
+        setDeviceOwner();
+
+        final Set<String> ssids = new ArraySet<>();
+        assertThrows(IllegalArgumentException.class,
+                () -> 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(
@@ -7863,18 +8202,6 @@
         // To allow creation of Notification via Notification.Builder
         mContext.applicationInfo = mRealTestContext.getApplicationInfo();
 
-        // Setup resources to render notification titles and texts.
-        when(mServiceContext.resources
-                .getString(R.string.personal_apps_suspension_title))
-                .thenReturn(PROFILE_OFF_SUSPENSION_TITLE);
-        when(mServiceContext.resources
-                .getString(R.string.personal_apps_suspension_text))
-                .thenReturn(PROFILE_OFF_SUSPENSION_TEXT);
-        when(mServiceContext.resources
-                .getString(eq(R.string.personal_apps_suspension_soon_text),
-                        anyString(), anyString(), anyInt()))
-                .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT);
-
         // Make locale available for date formatting:
         when(mServiceContext.resources.getConfiguration())
                 .thenReturn(mRealTestContext.getResources().getConfiguration());
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 d4b1165..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.
@@ -232,6 +233,10 @@
                 return mMockSystemServices.crossProfileApps;
             case Context.VPN_MANAGEMENT_SERVICE:
                 return mMockSystemServices.vpnManager;
+            case Context.DEVICE_POLICY_SERVICE:
+                return mMockSystemServices.devicePolicyManager;
+            case Context.LOCATION_SERVICE:
+                return mMockSystemServices.locationManager;
         }
         throw new UnsupportedOperationException();
     }
@@ -491,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 8a2919d..21fb2da 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -31,6 +31,7 @@
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.backup.IBackupManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
@@ -46,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;
@@ -80,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;
 
 /**
@@ -89,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;
@@ -125,6 +129,8 @@
     public final AppOpsManager appOpsManager;
     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();
@@ -136,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);
@@ -172,6 +179,8 @@
         appOpsManager = mock(AppOpsManager.class);
         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 c544f5c..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;
@@ -2046,7 +2053,7 @@
     private static NetworkStateSnapshot buildWifi() {
         WifiInfo mockWifiInfo = mock(WifiInfo.class);
         when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo);
-        when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
+        when(mockWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
         final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder()
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/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 408d2c5..99edecf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -16,6 +16,7 @@
 package com.android.server.pm;
 
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundlesEqual;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
@@ -257,6 +258,10 @@
                 .setLongLived(true)
                 .setExtras(pb)
                 .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
+                .addCapabilityBinding("action.intent.START_EXERCISE",
+                        "exercise.type", list("running", "jogging"))
+                .addCapabilityBinding("action.intent.START_EXERCISE",
+                        "exercise.duration", list("10m"))
                 .build();
         si.addFlags(ShortcutInfo.FLAG_PINNED);
         si.setBitmapPath("abc");
@@ -294,6 +299,13 @@
         assertEquals(null, si.getDisabledMessageResName());
         assertEquals("android:style/Theme.Black.NoTitleBar.Fullscreen",
                 si.getStartingThemeResName());
+        assertTrue(si.hasCapability("action.intent.START_EXERCISE"));
+        assertFalse(si.hasCapability(""));
+        assertFalse(si.hasCapability("random"));
+        assertEquals(list("running", "jogging"), si.getCapabilityParameterValues(
+                "action.intent.START_EXERCISE", "exercise.type"));
+        assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", ""));
+        assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random"));
     }
 
     public void testShortcutInfoParcel_resId() {
@@ -947,6 +959,10 @@
                 .setRank(123)
                 .setExtras(pb)
                 .setLocusId(new LocusId("1.2.3.4.5"))
+                .addCapabilityBinding("action.intent.START_EXERCISE",
+                        "exercise.type", list("running", "jogging"))
+                .addCapabilityBinding("action.intent.START_EXERCISE",
+                        "exercise.duration", list("10m"))
                 .build();
         sorig.setTimestamp(mInjectedCurrentTimeMillis);
 
@@ -1008,6 +1024,14 @@
         assertNull(si.getIconUri());
         assertTrue(si.getLastChangedTimestamp() < now);
 
+        assertTrue(si.hasCapability("action.intent.START_EXERCISE"));
+        assertFalse(si.hasCapability(""));
+        assertFalse(si.hasCapability("random"));
+        assertEquals(list("running", "jogging"), si.getCapabilityParameterValues(
+                "action.intent.START_EXERCISE", "exercise.type"));
+        assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", ""));
+        assertEmpty(si.getCapabilityParameterValues("action.intent.START_EXERCISE", "random"));
+
         // Make sure ranks are saved too.  Because of the auto-adjusting, we need two shortcuts
         // to test it.
         si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
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 429445f..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(),
@@ -664,6 +664,24 @@
         }
     }
 
+    // Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE.
+    @MediumTest
+    @Test
+    public void testCreateUser_disallowAddClonedUserProfile() throws Exception {
+        final int primaryUserId = ActivityManager.getCurrentUser();
+        final UserHandle primaryUserHandle = asHandle(primaryUserId);
+        mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE,
+                true, primaryUserHandle);
+        try {
+            UserInfo cloneProfileUserInfo = createProfileForUser("Clone",
+                    UserManager.USER_TYPE_PROFILE_CLONE, primaryUserId);
+            assertThat(cloneProfileUserInfo).isNull();
+        } finally {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false,
+                    primaryUserHandle);
+        }
+    }
+
     // Make sure createProfile would fail if we have DISALLOW_ADD_MANAGED_PROFILE.
     @MediumTest
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index d164d2a..0e98b5e 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -38,6 +39,7 @@
 import android.app.StatusBarManager;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.om.IOverlayManager;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -96,6 +98,10 @@
     private ApplicationInfo mApplicationInfo;
     @Mock
     private IStatusBar.Stub mMockStatusBar;
+    @Mock
+    private IOverlayManager mOverlayManager;
+    @Mock
+    private PackageManager mPackageManager;
     @Captor
     private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor;
 
@@ -130,6 +136,7 @@
                 mStatusBarManagerService);
 
         mStatusBarManagerService.registerStatusBar(mMockStatusBar);
+        mStatusBarManagerService.registerOverlayManager(mOverlayManager);
 
         mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus);
     }
@@ -577,27 +584,56 @@
     }
 
     @Test
-    public void testSetNavBarModeOverride_setsOverrideModeKids() {
+    public void testSetNavBarModeOverride_setsOverrideModeKids() throws RemoteException {
         int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
+
         mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids);
 
         assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride());
+        verify(mOverlayManager).setEnabledExclusiveInCategory(anyString(), anyInt());
     }
 
     @Test
-    public void testSetNavBarModeOverride_setsOverrideModeNone() {
+    public void testSetNavBarModeOverride_setsOverrideModeNone() throws RemoteException {
         int navBarModeOverrideNone = StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
+
         mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideNone);
 
         assertEquals(navBarModeOverrideNone, mStatusBarManagerService.getNavBarModeOverride());
+        verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
     }
 
     @Test
-    public void testSetNavBarModeOverride_invalidInputThrowsError() {
+    public void testSetNavBarModeOverride_invalidInputThrowsError() throws RemoteException {
         int navBarModeOverrideInvalid = -1;
 
         assertThrows(UnsupportedOperationException.class,
                 () -> mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideInvalid));
+        verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
+    }
+
+    @Test
+    public void testSetNavBarModeOverride_noOverlayManagerDoesNotEnable() throws RemoteException {
+        mOverlayManager = null;
+        int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
+
+        mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids);
+
+        assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride());
+        verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
+    }
+
+    @Test
+    public void testSetNavBarModeOverride_noPackageDoesNotEnable() throws Exception {
+        mContext.setMockPackageManager(mPackageManager);
+        when(mPackageManager.getPackageInfo(anyString(),
+                any(PackageManager.PackageInfoFlags.class))).thenReturn(null);
+        int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
+
+        mStatusBarManagerService.setNavBarModeOverride(navBarModeOverrideKids);
+
+        assertEquals(navBarModeOverrideKids, mStatusBarManagerService.getNavBarModeOverride());
+        verify(mOverlayManager, never()).setEnabledExclusiveInCategory(anyString(), anyInt());
     }
 
     private void mockUidCheck() {
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/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index 2c22419..5d4ffbb 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -319,6 +319,34 @@
         }
     }
 
+
+    @Test
+    public void shouldIgnoreVibration_vibrateOnDisabled_ignoresUsagesNotAccessibility() {
+        setUserSetting(Settings.System.VIBRATE_ON, 0);
+
+        for (int usage : ALL_USAGES) {
+            if (usage == USAGE_ACCESSIBILITY) {
+                assertVibrationNotIgnoredForUsage(usage);
+            } else {
+                assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS);
+            }
+            assertVibrationNotIgnoredForUsageAndFlags(usage,
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibration_vibrateOnEnabledOrUnset_allowsAnyUsage() {
+        deleteUserSetting(Settings.System.VIBRATE_ON);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+
+        setUserSetting(Settings.System.VIBRATE_ON, 1);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsage(usage);
+        }
+    }
     @Test
     public void shouldIgnoreVibration_withRingSettingsOff_disableRingtoneVibrations() {
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
@@ -560,10 +588,17 @@
         when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
     }
 
+    private void deleteUserSetting(String settingName) {
+        Settings.System.putStringForUser(
+                mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
+        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
+        mVibrationSettings.updateSettings();
+    }
+
     private void setUserSetting(String settingName, int value) {
         Settings.System.putIntForUser(
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
-        // FakeSettingsProvider don't support testing triggering ContentObserver yet.
+        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
         mVibrationSettings.updateSettings();
     }
 
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/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index a834e2b6..12cd834 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -394,7 +394,7 @@
                 "disabledMessage", 0, "disabledMessageResName",
                 null, null, 0, null, 0, 0,
                 0, "iconResName", "bitmapPath", null, 0,
-                null, null, null);
+                null, null, null, null);
         return si;
     }
 
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/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index b48c9c3..736fbd9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,6 +46,7 @@
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
+import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
 import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -4250,6 +4251,52 @@
     }
 
     @Test
+    public void testTooManyGroups() {
+        for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) {
+            NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i),
+                    String.valueOf(i));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        }
+        try {
+            NotificationChannelGroup group = new NotificationChannelGroup(
+                    String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT),
+                    String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+            fail("Allowed to create too many notification channel groups");
+        } catch (IllegalStateException e) {
+            // great
+        }
+    }
+
+    @Test
+    public void testTooManyGroups_xml() throws Exception {
+        String extraGroup = "EXTRA";
+        String extraGroup1 = "EXTRA1";
+
+        // create first... many... directly so we don't need a big xml blob in this test
+        for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) {
+            NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i),
+                    String.valueOf(i));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        }
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>"
+                + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>"
+                + "</package>"
+                + "</ranking>";
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O));
+        assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O));
+    }
+
+    @Test
     public void testRestoreMultiUser() throws Exception {
         String pkg = "restore_pkg";
         String channelId = "channelId";
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 0dcf799..30ad1f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -24,7 +24,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
@@ -41,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;
@@ -84,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;
@@ -571,7 +572,7 @@
         final ActivityRecord activity = createActivityWith2LevelTask();
         final Task task = activity.getTask();
         final Task rootTask = activity.getRootTask();
-        rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         final Rect stableRect = new Rect();
         rootTask.mDisplayContent.getStableRect(stableRect);
 
@@ -616,7 +617,7 @@
         spyOn(tda);
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
         final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         rootTask.setBounds(0, 0, 1000, 500);
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setParentTask(rootTask)
@@ -3306,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/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index a2b04c2..7c340ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -77,6 +77,7 @@
 import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -188,6 +189,9 @@
 
             @Override
             public void onFixedRotationFinished(int displayId) {}
+
+            @Override
+            public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {}
         };
         int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
         for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
new file mode 100644
index 0000000..687779d
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.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 com.android.server.wm;
+
+import static android.window.BackNavigationInfo.typeToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.hardware.HardwareBuffer;
+import android.platform.test.annotations.Presubmit;
+import android.window.BackNavigationInfo;
+import android.window.TaskSnapshot;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class BackNavigationControllerTests extends WindowTestsBase {
+
+    private BackNavigationController mBackNavigationController;
+
+    @Before
+    public void setUp() throws Exception {
+        mBackNavigationController = new BackNavigationController();
+    }
+
+    @Test
+    public void backTypeHomeWhenBackToLauncher() {
+        Task task = createTopTaskWithActivity();
+        BackNavigationInfo backNavigationInfo =
+                mBackNavigationController.startBackNavigation(task, new StubTransaction());
+        assertThat(backNavigationInfo).isNotNull();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+    }
+
+    @Test
+    public void backTypeCrossTaskWhenBackToPreviousTask() {
+        Task taskA = createTask(mDefaultDisplay);
+        createActivityRecord(taskA);
+        Task task = createTopTaskWithActivity();
+        BackNavigationInfo backNavigationInfo =
+                mBackNavigationController.startBackNavigation(task, new StubTransaction());
+        assertThat(backNavigationInfo).isNotNull();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+    }
+
+    @Test
+    public void backTypeCrossActivityWhenBackToPreviousActivity() {
+        Task task = createTopTaskWithActivity();
+        mAtm.setFocusedTask(task.mTaskId, createActivityRecord(task));
+        BackNavigationInfo backNavigationInfo =
+                mBackNavigationController.startBackNavigation(task, new StubTransaction());
+        assertThat(backNavigationInfo).isNotNull();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+    }
+
+    /**
+     * Checks that we are able to fill all the field of the {@link BackNavigationInfo} object.
+     */
+    @Test
+    public void backNavInfoFullyPopulated() {
+        Task task = createTopTaskWithActivity();
+        createActivityRecord(task);
+
+        // We need a mock screenshot so
+        TaskSnapshotController taskSnapshotController = createMockTaskSnapshotController();
+
+        mBackNavigationController.setTaskSnapshotController(taskSnapshotController);
+
+        BackNavigationInfo backNavigationInfo =
+                mBackNavigationController.startBackNavigation(task, new StubTransaction());
+        assertThat(backNavigationInfo).isNotNull();
+        assertThat(backNavigationInfo.getDepartingWindowContainer()).isNotNull();
+        assertThat(backNavigationInfo.getScreenshotSurface()).isNotNull();
+        assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNotNull();
+        assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
+    }
+
+    @NonNull
+    private TaskSnapshotController createMockTaskSnapshotController() {
+        TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
+        TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
+        when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
+        when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean()))
+                .thenReturn(taskSnapshot);
+        return taskSnapshotController;
+    }
+
+    @NonNull
+    private Task createTopTaskWithActivity() {
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord record = createActivityRecord(task);
+        when(record.mSurfaceControl.isValid()).thenReturn(true);
+        mAtm.setFocusedTask(task.mTaskId, record);
+        return task;
+    }
+}
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 2f78b58..ea03250 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -119,6 +119,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -1101,7 +1102,7 @@
         final DisplayContent dc = createNewDisplay();
         dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app"));
         dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode(
-                WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+                WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
         assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
     }
 
@@ -1152,7 +1153,7 @@
         final DisplayContent dc = createNewDisplay();
         dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
         dc.getImeTarget(IME_TARGET_INPUT).getWindow().setWindowingMode(
-                WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+                WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
         dc.setImeLayeringTarget(dc.getImeTarget(IME_TARGET_INPUT).getWindow());
         dc.setRemoteInsetsController(createDisplayWindowInsetsController());
         assertNotEquals(dc.getImeTarget(IME_TARGET_INPUT).getWindow(),
@@ -1339,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) {
@@ -1353,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,
@@ -1391,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.
@@ -1481,7 +1482,7 @@
         assertFalse(app.hasFixedRotationTransform());
         assertFalse(app2.hasFixedRotationTransform());
         assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-        assertNull(mDisplayContent.getFadeRotationAnimationController());
+        assertNull(mDisplayContent.getAsyncRotationController());
     }
 
     @Test
@@ -1597,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);
@@ -1982,6 +2007,7 @@
         // Test step 1: appWin1 is the current IME target and soft-keyboard is visible.
         mDisplayContent.computeImeTarget(true);
         assertEquals(appWin1, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
+        mDisplayContent.setImeInputTarget(appWin1);
         spyOn(mDisplayContent.mInputMethodWindow);
         doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
         mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
@@ -1998,7 +2024,6 @@
         // be shown at this time.
         final Transaction t = mDisplayContent.getPendingTransaction();
         spyOn(t);
-        mDisplayContent.setImeInputTarget(appWin2);
         mDisplayContent.computeImeTarget(true);
         assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
         assertTrue(mDisplayContent.shouldImeAttachedToApp());
@@ -2436,6 +2461,31 @@
         mockSession.finishMocking();
     }
 
+    @Test
+    public void testKeepClearAreasMultipleWindows() {
+        final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
+        final Rect rect1 = new Rect(0, 0, 10, 10);
+        w1.setKeepClearAreas(Arrays.asList(rect1));
+        final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2");
+        final Rect rect2 = new Rect(10, 10, 20, 20);
+        w2.setKeepClearAreas(Arrays.asList(rect2));
+
+        // No keep clear areas on display, because the windows are not visible
+        assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas());
+
+        makeWindowVisible(w1);
+
+        // The returned keep-clear areas contain the areas just from the visible window
+        assertEquals(new ArraySet(Arrays.asList(rect1)),
+                     new ArraySet(mDisplayContent.getKeepClearAreas()));
+
+        makeWindowVisible(w1, w2);
+
+        // The returned keep-clear areas contain the areas from all visible windows
+        assertEquals(new ArraySet(Arrays.asList(rect1, rect2)),
+                     new ArraySet(mDisplayContent.getKeepClearAreas()));
+    }
+
     private class TestToken extends Binder {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index acf6dc5..497ae1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -31,10 +31,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
-import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
@@ -44,7 +42,6 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
-import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
@@ -210,24 +207,6 @@
         expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
     }
 
-    @Test
-    public void layoutHint_appWindow() {
-        mWindow.mAttrs.setFitInsetsTypes(0);
-
-        final DisplayCutout.ParcelableWrapper outDisplayCutout =
-                new DisplayCutout.ParcelableWrapper();
-        final InsetsState outState = new InsetsState();
-
-        mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outState,
-                true /* localClient */);
-
-        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
-        assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
-                is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
-        assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(),
-                is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT)));
-    }
-
     /**
      * Verify that {@link DisplayPolicy#simulateLayoutDisplay} outputs the same display frames as
      * the real one.
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/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 94bc7f2..2eece4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
@@ -246,7 +245,7 @@
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
         app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
         child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
-        child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
 
         mDisplayContent.computeImeTarget(true);
         mDisplayContent.setLayoutNeeded();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index fc298b0..0c2de5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -334,7 +333,7 @@
         params.mWindowingMode = windowingMode;
         final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setCreateParentTask(true).build();
-        task.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        task.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
 
         mController.registerModifier(positioner);
 
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/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 3cb0bed..65b5cf5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -88,6 +88,7 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -529,6 +530,7 @@
 
     // TODO(b/199236198): check this is unnecessary or need to migrate after remove legacy split.
     @Test
+    @Ignore
     public void testShouldBeVisible_SplitScreen() {
         // task not supporting split should be fullscreen for this test.
         final Task notSupportingSplitTask = createTaskForShouldBeVisibleTest(
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 4069f0f..f4abf88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -21,8 +21,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.TYPE_VIRTUAL;
@@ -458,7 +458,7 @@
         final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer
                 .getDefaultTaskDisplayArea();
         final Task task = defaultTaskDisplayArea.createRootTask(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */);
         final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
 
         // Created tasks are focusable by default.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c722b0a..b815c38 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -77,6 +77,7 @@
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
@@ -2182,6 +2183,29 @@
                 .computeAspectRatio(sizeCompatAppBounds), delta);
     }
 
+    @Test
+    public void testClearSizeCompat_resetOverrideConfig() {
+        final int origDensity = 480;
+        final int newDensity = 520;
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 600, 800)
+                .setDensityDpi(origDensity)
+                .build();
+        setUpApp(display);
+        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Activity should enter size compat with old density after display density change.
+        display.setForcedDensity(newDensity, UserHandle.USER_CURRENT);
+
+        assertScaled();
+        assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
+
+        // Activity should exit size compat with new density.
+        mActivity.clearSizeCompatMode();
+
+        assertFitted();
+        assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
+    }
+
     private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
             float letterboxHorizontalPositionMultiplier) {
         // Set up a display in landscape and ignoring orientation request.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index cdf6b59..80f6bce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -25,8 +25,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
@@ -355,14 +353,10 @@
                 true /* reuseCandidate */);
         assertGetOrCreateRootTask(WINDOWING_MODE_UNDEFINED, type, candidateTask,
                 true /* reuseCandidate */);
-        assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, type, candidateTask,
-                true /* reuseCandidate */);
         assertGetOrCreateRootTask(WINDOWING_MODE_FREEFORM, type, candidateTask,
                 true /* reuseCandidate */);
         assertGetOrCreateRootTask(WINDOWING_MODE_MULTI_WINDOW, type, candidateTask,
                 true /* reuseCandidate */);
-        assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, type, candidateTask,
-                false /* reuseCandidate */);
         assertGetOrCreateRootTask(WINDOWING_MODE_PINNED, type, candidateTask,
                 true /* reuseCandidate */);
 
@@ -388,7 +382,7 @@
 
         final Task primarySplitTask = new TaskBuilder(mSupervisor)
                 .setTaskDisplayArea(defaultTaskDisplayArea)
-                .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
                 .setOnTop(true)
                 .setCreateActivity(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/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index f138475..64959f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -17,8 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -50,11 +49,8 @@
     @Test
     public void testDockedDividerPosition() {
         final WindowState splitScreenWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
                 mDisplayContent, "splitScreenWindow");
-        final WindowState splitScreenSecondaryWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
-                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
 
         mDisplayContent.setImeLayeringTarget(splitScreenWindow);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 75a87ba..4d5fb6d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -24,8 +24,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -520,16 +518,16 @@
         DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
 
         Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+                dc, WINDOWING_MODE_FULLSCREEN, null);
         RunningTaskInfo info1 = task1.getTaskInfo();
-        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+        assertEquals(WINDOWING_MODE_FULLSCREEN,
                 info1.configuration.windowConfiguration.getWindowingMode());
         assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
 
         Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+                dc, WINDOWING_MODE_MULTI_WINDOW, null);
         RunningTaskInfo info2 = task2.getTaskInfo();
-        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 info2.configuration.windowConfiguration.getWindowingMode());
         assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType);
 
@@ -539,7 +537,7 @@
         assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token));
         infos = getTasksCreatedByOrganizer(dc);
         assertEquals(1, infos.size());
-        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, infos.get(0).getWindowingMode());
     }
 
     @Test
@@ -577,7 +575,7 @@
         final StubOrganizer listener = new StubOrganizer();
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
         Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         RunningTaskInfo info1 = task.getTaskInfo();
 
         final Task rootTask = createTask(
@@ -626,7 +624,7 @@
         };
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
         Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         RunningTaskInfo info1 = task.getTaskInfo();
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -684,10 +682,10 @@
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
 
         Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         RunningTaskInfo info1 = task1.getTaskInfo();
         Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         RunningTaskInfo info2 = task2.getTaskInfo();
         // Ensure events dispatch to organizer.
         mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1056,7 +1054,7 @@
     public void testReparentToOrganizedTask() {
         final ITaskOrganizer organizer = registerMockOrganizer();
         Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         final Task task1 = createRootTask();
         final Task task2 = createTask(rootTask, false /* fakeDraw */);
         WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -1223,7 +1221,7 @@
         final Task rootTask = activity.getRootTask();
         rootTask.setResizeMode(activity.info.resizeMode);
         final Task splitPrimaryRootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
-                mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null);
+                mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
                 splitPrimaryRootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ec8ec2b..80192f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -83,6 +82,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.view.Gravity;
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
@@ -265,22 +265,19 @@
         assertFalse(appWindow.canBeImeTarget());
         assertFalse(imeWindow.canBeImeTarget());
 
-        // Simulate the window is in split screen primary root task and the current state is
-        // minimized and home root task is resizable, so that we should ignore input for the
-        // root task.
+        // Simulate the window is in split screen root task.
         final DockedTaskDividerController controller =
                 mDisplayContent.getDockedDividerController();
         final Task rootTask = createTask(mDisplayContent,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         spyOn(appWindow);
         spyOn(controller);
         spyOn(rootTask);
         rootTask.setFocusable(false);
         doReturn(rootTask).when(appWindow).getRootTask();
 
-        // Make sure canBeImeTarget is false due to shouldIgnoreInput is true;
+        // Make sure canBeImeTarget is false;
         assertFalse(appWindow.canBeImeTarget());
-        assertTrue(rootTask.shouldIgnoreInput());
     }
 
     @Test
@@ -727,8 +724,9 @@
     @Test
     public void testCantReceiveTouchWhenNotFocusable() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
-        win0.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        win0.mActivityRecord.getRootTask().setFocusable(false);
+        final Task rootTask = win0.mActivityRecord.getRootTask();
+        spyOn(rootTask);
+        when(rootTask.shouldIgnoreInput()).thenReturn(true);
         assertFalse(win0.canReceiveTouchInput());
     }
 
@@ -928,8 +926,8 @@
         mDisplayContent.setImeLayeringTarget(mAppWindow);
 
         // Simulate entering multi-window mode and verify if the IME control target is remote.
-        app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode());
+        app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode());
         assertEquals(mDisplayContent.mRemoteInsetsControlTarget,
                 mDisplayContent.computeImeControlTarget());
 
@@ -944,14 +942,13 @@
 
     @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
     @Test
-    public void testNotificationShadeHasImeInsetsWhenSplitscreenActivated() {
+    public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
         WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
                 mAppWindow.mToken, "app");
 
-        // Simulate entering multi-window mode and verify if the split-screen is activated.
-        app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode());
-        assertTrue(mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated());
+        // Simulate entering multi-window mode and windowing mode is multi-window.
+        app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode());
 
         // Simulate notificationShade is shown and being IME layering target.
         mNotificationShadeWindow.setHasSurface(true);
@@ -965,7 +962,7 @@
         mDisplayContent.getInsetsStateController().getRawInsetsState()
                 .setSourceVisible(ITYPE_IME, true);
 
-        // Verify notificationShade can still get IME insets even the split-screen is activated.
+        // Verify notificationShade can still get IME insets even windowing mode is multi-window.
         InsetsState state = mDisplayContent.getInsetsStateController().getInsetsForWindow(
                 mNotificationShadeWindow);
         assertNotNull(state.peekSource(ITYPE_IME));
@@ -986,4 +983,40 @@
         assertFalse(app.isVisible());
         assertTrue(app.isVisibleRequested());
     }
+
+    @Test
+    public void testKeepClearAreas() {
+        final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+        makeWindowVisible(window);
+
+        final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
+        final Rect keepClearArea2 = new Rect(5, 10, 15, 20);
+        final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2);
+        window.setKeepClearAreas(keepClearAreas);
+
+        // Test that the keep-clear rects are stored and returned
+        assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas()));
+
+        // Test that keep-clear rects are overwritten
+        window.setKeepClearAreas(Arrays.asList());
+        assertEquals(0, window.getKeepClearAreas().size());
+
+        // Move the window position
+        final SurfaceControl.Transaction t = spy(StubTransaction.class);
+        window.mSurfaceControl = mock(SurfaceControl.class);
+        final Rect frame = window.getFrame();
+        frame.set(10, 20, 60, 80);
+        window.updateSurfacePosition(t);
+        assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition);
+
+        // Test that the returned keep-clear rects are translated to display space
+        window.setKeepClearAreas(keepClearAreas);
+        Rect expectedArea1 = new Rect(keepClearArea1);
+        expectedArea1.offset(frame.left, frame.top);
+        Rect expectedArea2 = new Rect(keepClearArea2);
+        expectedArea2.offset(frame.left, frame.top);
+
+        assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)),
+                     new ArraySet(window.getKeepClearAreas()));
+    }
 }
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/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 4dffe7e..0f223ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -22,7 +22,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -363,7 +362,7 @@
                 ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
                 "pinnedStackWindow");
         final WindowState dockedStackWindow = createWindow(null,
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
                 mDisplayContent, "dockedStackWindow");
         final WindowState assistantStackWindow = createWindow(null,
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
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/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 88725a6..0d88a0d 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -61,6 +61,7 @@
 import android.os.storage.StorageEventListener;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -70,6 +71,7 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
@@ -128,6 +130,12 @@
     private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>>
             mStorageStatsAugmenters = new CopyOnWriteArrayList<>();
 
+    @GuardedBy("mLock")
+    private int
+            mStorageThresholdPercentHigh = StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH;
+
+    private final Object mLock = new Object();
+
     public StorageStatsService(Context context) {
         mContext = Preconditions.checkNotNull(context);
         mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
@@ -173,6 +181,19 @@
                 }
             }
         }, prFilter);
+
+        updateConfig();
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                mContext.getMainExecutor(), properties -> updateConfig());
+    }
+
+    private void updateConfig() {
+        synchronized (mLock) {
+            mStorageThresholdPercentHigh = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                    StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY,
+                    StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH);
+        }
     }
 
     private void invalidateMounts() {
@@ -554,7 +575,7 @@
          * By only triggering a re-calculation after the storage has changed sizes, we can avoid
          * recalculating quotas too often. Minimum change delta high and low define the
          * percentage of change we need to see before we recalculate quotas when the device has
-         * enough storage space (more than StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total
+         * enough storage space (more than mStorageThresholdPercentHigh of total
          * free) and in low storage condition respectively.
          */
         private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5;
@@ -588,11 +609,15 @@
                     mStats.restat(Environment.getDataDirectory().getAbsolutePath());
                     long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes());
                     long bytesDeltaThreshold;
-                    if (mStats.getAvailableBytes() >  mTotalBytes
-                            * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH / 100) {
-                        bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100;
-                    } else {
-                        bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100;
+                    synchronized (mLock) {
+                        if (mStats.getAvailableBytes() >  mTotalBytes
+                                * mStorageThresholdPercentHigh / 100) {
+                            bytesDeltaThreshold = mTotalBytes
+                                    * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100;
+                        } else {
+                            bytesDeltaThreshold = mTotalBytes
+                                    * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100;
+                        }
                     }
                     if (bytesDelta > bytesDeltaThreshold) {
                         mPreviousBytes = mStats.getAvailableBytes();
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/Android.bp b/services/usb/Android.bp
index 01feacd..3b50fa4 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -29,6 +29,7 @@
         "android.hardware.usb-V1.1-java",
         "android.hardware.usb-V1.2-java",
         "android.hardware.usb-V1.3-java",
+	"android.hardware.usb-V1-java",
         "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.usb.gadget-V1.1-java",
         "android.hardware.usb.gadget-V1.2-java",
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 0aa62c5..1c72eb8 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -257,10 +257,11 @@
 
         // look for MIDI devices
         boolean hasMidi = parser.hasMIDIInterface();
-        int midiNumInputs = parser.calculateNumMidiInputs();
-        int midiNumOutputs = parser.calculateNumMidiOutputs();
+        int midiNumInputs = parser.calculateNumLegacyMidiInputs();
+        int midiNumOutputs = parser.calculateNumLegacyMidiOutputs();
         if (DEBUG) {
             Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
+            Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs);
         }
         if (hasMidi && mHasMidiFeature) {
             int device = 0;
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index f33001c..9ac270f 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbDevice;
 import android.os.Bundle;
@@ -46,6 +47,8 @@
 import com.android.server.usb.descriptors.report.TextReportCanvas;
 import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
 
+import libcore.io.IoUtils;
+
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
@@ -90,6 +93,13 @@
     private ConnectionRecord mLastConnect;
     private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
 
+    /**
+     * List of connected MIDI devices
+     */
+    private final HashMap<String, UsbUniversalMidiDevice>
+            mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
+    private final boolean mHasMidiFeature;
+
     /*
      * ConnectionRecord
      * Stores connection/disconnection data.
@@ -245,6 +255,7 @@
             setUsbDeviceConnectionHandler(ComponentName.unflattenFromString(
                     deviceConnectionHandler));
         }
+        mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
     }
 
     public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
@@ -413,6 +424,18 @@
 
                 mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
 
+                if (mHasMidiFeature) {
+                    if (parser.containsUniversalMidiDeviceEndpoint()) {
+                        UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
+                                newDevice, parser);
+                        if (midiDevice != null) {
+                            mMidiDevices.put(deviceAddress, midiDevice);
+                        } else {
+                            Slog.e(TAG, "Universal Midi Device is null.");
+                        }
+                    }
+                }
+
                 // Tracking
                 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
                         parser.getRawDescriptors());
@@ -446,6 +469,14 @@
                 Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName());
                 mUsbAlsaManager.usbDeviceRemoved(deviceAddress);
                 mPermissionManager.usbDeviceRemoved(device);
+
+                // MIDI
+                UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
+                if (midiDevice != null) {
+                    Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
+                    IoUtils.closeQuietly(midiDevice);
+                }
+
                 getCurrentUserSettings().usbDeviceRemoved(device);
                 ConnectionRecord current = mConnected.get(deviceAddress);
                 // Tracking
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index b61e93b..275f217 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -303,7 +303,8 @@
         }
 
         mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs,
-                null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
+                null, null, properties, MidiDeviceInfo.TYPE_USB,
+                MidiDeviceInfo.PROTOCOL_UNKNOWN, mCallback);
         if (mServer == null) {
             return false;
         }
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index ec28040..65b79bf 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usb;
 
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
@@ -25,6 +27,12 @@
 import static android.hardware.usb.UsbPortStatus.MODE_UFP;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SOURCE;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SINK;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_HOST;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_DEVICE;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_DFP;
+import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_UFP;
 
 import static com.android.internal.usb.DumpUtils.writePort;
 import static com.android.internal.usb.DumpUtils.writePortStatus;
@@ -38,6 +46,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
@@ -74,9 +83,13 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
+import com.android.server.usb.hal.port.RawPortInfo;
+import com.android.server.usb.hal.port.UsbPortHal;
+import com.android.server.usb.hal.port.UsbPortHalInstance;
 
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /**
  * Allows trusted components to control the properties of physical USB ports
@@ -109,16 +122,9 @@
     // The system context.
     private final Context mContext;
 
-    // Proxy object for the usb hal daemon.
-    @GuardedBy("mLock")
-    private IUsb mProxy = null;
-
     // Callback when the UsbPort status is changed by the kernel.
     // Mostly due a command sent by the remote Usb device.
-    private HALCallback mHALCallback = new HALCallback(null, this);
-
-    // Cookie sent for usb hal death notification.
-    private static final int USB_HAL_DEATH_COOKIE = 1000;
+    //private HALCallback mHALCallback = new HALCallback(null, this);
 
     // Used as the key while sending the bundle to Main thread.
     private static final String PORT_INFO = "port_info";
@@ -156,36 +162,23 @@
      */
     private int mIsPortContaminatedNotificationId;
 
-    private boolean mEnableUsbDataSignaling;
-    protected int mCurrentUsbHalVersion;
+    private UsbPortHal mUsbPortHal;
+
+    private long mTransactionId;
 
     public UsbPortManager(Context context) {
         mContext = context;
-        try {
-            ServiceNotification serviceNotification = new ServiceNotification();
-
-            boolean ret = IServiceManager.getService()
-                    .registerForNotifications("android.hardware.usb@1.0::IUsb",
-                            "", serviceNotification);
-            if (!ret) {
-                logAndPrint(Log.ERROR, null,
-                        "Failed to register service start notification");
-            }
-        } catch (RemoteException e) {
-            logAndPrintException(null,
-                    "Failed to register service start notification", e);
-            return;
-        }
-        connectToProxy(null);
+        mUsbPortHal = UsbPortHalInstance.getInstance(this, null);
+        logAndPrint(Log.DEBUG, null, "getInstance done");
     }
 
     public void systemReady() {
-	mSystemReady = true;
-        if (mProxy != null) {
+        mSystemReady = true;
+        if (mUsbPortHal != null) {
+            mUsbPortHal.systemReady();
             try {
-                mProxy.queryPortStatus();
-                mEnableUsbDataSignaling = true;
-            } catch (RemoteException e) {
+                mUsbPortHal.queryPortStatus(++mTransactionId);
+            } catch (Exception e) {
                 logAndPrintException(null,
                         "ServiceStart: Failed to query port status", e);
             }
@@ -233,6 +226,7 @@
             intent.setComponent(ComponentName.unflattenFromString(r.getString(
                     com.android.internal.R.string.config_usbContaminantActivity)));
             intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
+            intent.putExtra(UsbManager.EXTRA_PORT_STATUS, currentPortInfo.mUsbPortStatus);
 
             // Simple notification clicks are immutable
             PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
@@ -340,13 +334,92 @@
         }
 
         try {
-            // Oneway call into the hal. Use the castFrom method from HIDL.
-            android.hardware.usb.V1_2.IUsb proxy = android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
-            proxy.enableContaminantPresenceDetection(portId, enable);
-        } catch (RemoteException e) {
+            mUsbPortHal.enableContaminantPresenceDetection(portId, enable, ++mTransactionId);
+        } catch (Exception e) {
             logAndPrintException(pw, "Failed to set contaminant detection", e);
-        } catch (ClassCastException e) {
-            logAndPrintException(pw, "Method only applicable to V1.2 or above implementation", e);
+        }
+    }
+
+    /**
+     * Limits power transfer in/out of USB-C port.
+     *
+     * @param portId port identifier.
+     * @param limit limit power transfer when true.
+     */
+    public void enableLimitPowerTransfer(@NonNull String portId, boolean limit, long transactionId,
+            IUsbOperationInternal callback, IndentingPrintWriter pw) {
+        Objects.requireNonNull(portId);
+        final PortInfo portInfo = mPorts.get(portId);
+        if (portInfo == null) {
+            logAndPrint(Log.ERROR, pw, "enableLimitPowerTransfer: No such port: " + portId
+                    + " opId:" + transactionId);
+            try {
+                if (callback != null) {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(pw,
+                        "enableLimitPowerTransfer: Failed to call OperationComplete. opId:"
+                        + transactionId, e);
+            }
+            return;
+        }
+
+        try {
+            try {
+                mUsbPortHal.enableLimitPowerTransfer(portId, limit, transactionId, callback);
+            } catch (Exception e) {
+                logAndPrintException(pw,
+                    "enableLimitPowerTransfer: Failed to limit power transfer. opId:"
+                    + transactionId , e);
+                if (callback != null) {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                }
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw,
+                    "enableLimitPowerTransfer:Failed to call onOperationComplete. opId:"
+                    + transactionId, e);
+        }
+    }
+
+    /**
+     * 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);
         }
     }
 
@@ -355,46 +428,79 @@
      *
      * @param enable enable or disable USB data signaling
      */
-    public boolean enableUsbDataSignal(boolean enable) {
-        try {
-            mEnableUsbDataSignaling = enable;
-            // Call into the hal. Use the castFrom method from HIDL.
-            android.hardware.usb.V1_3.IUsb proxy = android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
-            return proxy.enableUsbDataSignal(enable);
-        } catch (RemoteException e) {
-            logAndPrintException(null, "Failed to set USB data signaling", e);
-            return false;
-        } catch (ClassCastException e) {
-            logAndPrintException(null, "Method only applicable to V1.3 or above implementation", e);
+    public boolean enableUsbData(@NonNull String portId, boolean enable, int transactionId,
+            @NonNull IUsbOperationInternal callback, IndentingPrintWriter pw) {
+        Objects.requireNonNull(callback);
+        Objects.requireNonNull(portId);
+        final PortInfo portInfo = mPorts.get(portId);
+        if (portInfo == null) {
+            logAndPrint(Log.ERROR, pw, "enableUsbData: No such port: " + portId
+                    + " opId:" + transactionId);
+            try {
+                callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH);
+            } catch (RemoteException e) {
+                logAndPrintException(pw,
+                        "enableUsbData: Failed to call OperationComplete. opId:"
+                        + transactionId, e);
+            }
             return false;
         }
+
+        try {
+            try {
+                return mUsbPortHal.enableUsbData(portId, enable, transactionId, callback);
+            } catch (Exception e) {
+                logAndPrintException(pw,
+                    "enableUsbData: Failed to invoke enableUsbData. opId:"
+                    + transactionId , e);
+                callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(pw,
+                    "enableUsbData: Failed to call onOperationComplete. opId:"
+                    + transactionId, e);
+        }
+
+        return false;
     }
 
     /**
      * Get USB HAL version
      *
      * @param none
+     * @return {@link UsbManager#USB_HAL_RETRY} returned when hal version
+     *         is yet to be determined.
      */
     public int getUsbHalVersion() {
-        return mCurrentUsbHalVersion;
+        if (mUsbPortHal != null) {
+            try {
+                return mUsbPortHal.getUsbHalVersion();
+            } catch (RemoteException e) {
+                return UsbManager.USB_HAL_RETRY;
+            }
+        }
+        return UsbManager.USB_HAL_RETRY;
     }
 
-    /**
-     * update USB HAL version
-     *
-     * @param none
-     */
-    private void updateUsbHalVersion() {
-        if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) {
-            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_3;
-        } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) {
-            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_2;
-        } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) {
-            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_1;
-        } else {
-            mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0;
-        }
-        logAndPrint(Log.INFO, null, "USB HAL version: " + mCurrentUsbHalVersion);
+    private int toHalUsbDataRole(int usbDataRole) {
+        if (usbDataRole == DATA_ROLE_DEVICE)
+            return HAL_DATA_ROLE_DEVICE;
+        else
+            return HAL_DATA_ROLE_HOST;
+    }
+
+    private int toHalUsbPowerRole(int usbPowerRole) {
+        if (usbPowerRole == POWER_ROLE_SINK)
+            return HAL_POWER_ROLE_SINK;
+        else
+            return HAL_POWER_ROLE_SOURCE;
+    }
+
+    private int toHalUsbMode(int usbMode) {
+        if (usbMode == MODE_UFP)
+            return HAL_MODE_UFP;
+        else
+            return HAL_MODE_DFP;
     }
 
     public void setPortRoles(String portId, int newPowerRole, int newDataRole,
@@ -473,7 +579,7 @@
                 sim.currentPowerRole = newPowerRole;
                 sim.currentDataRole = newDataRole;
                 updatePortsLocked(pw, null);
-            } else if (mProxy != null) {
+            } else if (mUsbPortHal != null) {
                 if (currentMode != newMode) {
                     // Changing the mode will have the side-effect of also changing
                     // the power and data roles but it might take some time to apply
@@ -485,44 +591,37 @@
                     logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: "
                             + "portId=" + portId
                             + ", newMode=" + UsbPort.modeToString(newMode));
-                    PortRole newRole = new PortRole();
-                    newRole.type = PortRoleType.MODE;
-                    newRole.role = newMode;
                     try {
-                        mProxy.switchRole(portId, newRole);
-                    } catch (RemoteException e) {
+                        mUsbPortHal.switchMode(portId, toHalUsbMode(newMode), ++mTransactionId);
+                    } catch (Exception e) {
                         logAndPrintException(pw, "Failed to set the USB port mode: "
                                 + "portId=" + portId
-                                + ", newMode=" + UsbPort.modeToString(newRole.role), e);
+                                + ", newMode=" + UsbPort.modeToString(newMode), e);
                     }
                 } else {
                     // Change power and data role independently as needed.
                     if (currentPowerRole != newPowerRole) {
-                        PortRole newRole = new PortRole();
-                        newRole.type = PortRoleType.POWER_ROLE;
-                        newRole.role = newPowerRole;
                         try {
-                            mProxy.switchRole(portId, newRole);
-                        } catch (RemoteException e) {
+                            mUsbPortHal.switchPowerRole(portId, toHalUsbPowerRole(newPowerRole),
+                                    ++mTransactionId);
+                        } catch (Exception e) {
                             logAndPrintException(pw, "Failed to set the USB port power role: "
                                             + "portId=" + portId
                                             + ", newPowerRole=" + UsbPort.powerRoleToString
-                                            (newRole.role),
+                                            (newPowerRole),
                                     e);
                             return;
                         }
                     }
                     if (currentDataRole != newDataRole) {
-                        PortRole newRole = new PortRole();
-                        newRole.type = PortRoleType.DATA_ROLE;
-                        newRole.role = newDataRole;
                         try {
-                            mProxy.switchRole(portId, newRole);
-                        } catch (RemoteException e) {
+                            mUsbPortHal.switchDataRole(portId, toHalUsbDataRole(newDataRole),
+                                    ++mTransactionId);
+                        } catch (Exception e) {
                             logAndPrintException(pw, "Failed to set the USB port data role: "
                                             + "portId=" + portId
-                                            + ", newDataRole=" + UsbPort.dataRoleToString(newRole
-                                            .role),
+                                            + ", newDataRole=" + UsbPort.dataRoleToString
+                                            (newDataRole),
                                     e);
                         }
                     }
@@ -531,6 +630,15 @@
         }
     }
 
+    public void updatePorts(ArrayList<RawPortInfo> newPortInfo) {
+        Message message = mHandler.obtainMessage();
+        Bundle bundle = new Bundle();
+        bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+        message.what = MSG_UPDATE_PORTS;
+        message.setData(bundle);
+        mHandler.sendMessage(message);
+    }
+
     public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
         synchronized (mLock) {
             if (mSimulatedPorts.containsKey(portId)) {
@@ -662,191 +770,12 @@
                 portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS);
             }
 
-            dump.write("enable_usb_data_signaling", UsbPortManagerProto.ENABLE_USB_DATA_SIGNALING,
-                    mEnableUsbDataSignaling);
+            dump.write("usb_hal_version", UsbPortManagerProto.HAL_VERSION, getUsbHalVersion());
         }
 
         dump.end(token);
     }
 
-    private static class HALCallback extends IUsbCallback.Stub {
-        public IndentingPrintWriter pw;
-        public UsbPortManager portManager;
-
-        HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) {
-            this.pw = pw;
-            this.portManager = portManager;
-        }
-
-        public void notifyPortStatusChange(
-                ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
-            if (!portManager.mSystemReady) {
-                return;
-            }
-
-            if (retval != Status.SUCCESS) {
-                logAndPrint(Log.ERROR, pw, "port status enquiry failed");
-                return;
-            }
-
-            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
-
-            for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
-                RawPortInfo temp = new RawPortInfo(current.portName,
-                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
-                        current.currentMode,
-                        current.canChangeMode, current.currentPowerRole,
-                        current.canChangePowerRole,
-                        current.currentDataRole, current.canChangeDataRole,
-                        false, CONTAMINANT_PROTECTION_NONE,
-                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
-                newPortInfo.add(temp);
-                logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName);
-            }
-
-            Message message = portManager.mHandler.obtainMessage();
-            Bundle bundle = new Bundle();
-            bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
-            message.what = MSG_UPDATE_PORTS;
-            message.setData(bundle);
-            portManager.mHandler.sendMessage(message);
-        }
-
-
-        public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus,
-                int retval) {
-            if (!portManager.mSystemReady) {
-                return;
-            }
-
-            if (retval != Status.SUCCESS) {
-                logAndPrint(Log.ERROR, pw, "port status enquiry failed");
-                return;
-            }
-
-            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
-
-            int numStatus = currentPortStatus.size();
-            for (int i = 0; i < numStatus; i++) {
-                PortStatus_1_1 current = currentPortStatus.get(i);
-                RawPortInfo temp = new RawPortInfo(current.status.portName,
-                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
-                        current.currentMode,
-                        current.status.canChangeMode, current.status.currentPowerRole,
-                        current.status.canChangePowerRole,
-                        current.status.currentDataRole, current.status.canChangeDataRole,
-                        false, CONTAMINANT_PROTECTION_NONE,
-                        false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
-                newPortInfo.add(temp);
-                logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName);
-            }
-
-            Message message = portManager.mHandler.obtainMessage();
-            Bundle bundle = new Bundle();
-            bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
-            message.what = MSG_UPDATE_PORTS;
-            message.setData(bundle);
-            portManager.mHandler.sendMessage(message);
-        }
-
-        public void notifyPortStatusChange_1_2(
-                ArrayList<PortStatus> currentPortStatus, int retval) {
-            if (!portManager.mSystemReady) {
-                return;
-            }
-
-            if (retval != Status.SUCCESS) {
-                logAndPrint(Log.ERROR, pw, "port status enquiry failed");
-                return;
-            }
-
-            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
-
-            int numStatus = currentPortStatus.size();
-            for (int i = 0; i < numStatus; i++) {
-                PortStatus current = currentPortStatus.get(i);
-                RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
-                        current.status_1_1.supportedModes,
-                        current.supportedContaminantProtectionModes,
-                        current.status_1_1.currentMode,
-                        current.status_1_1.status.canChangeMode,
-                        current.status_1_1.status.currentPowerRole,
-                        current.status_1_1.status.canChangePowerRole,
-                        current.status_1_1.status.currentDataRole,
-                        current.status_1_1.status.canChangeDataRole,
-                        current.supportsEnableContaminantPresenceProtection,
-                        current.contaminantProtectionStatus,
-                        current.supportsEnableContaminantPresenceDetection,
-                        current.contaminantDetectionStatus);
-                newPortInfo.add(temp);
-                logAndPrint(Log.INFO, pw, "ClientCallback V1_2: "
-                        + current.status_1_1.status.portName);
-            }
-
-            Message message = portManager.mHandler.obtainMessage();
-            Bundle bundle = new Bundle();
-            bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
-            message.what = MSG_UPDATE_PORTS;
-            message.setData(bundle);
-            portManager.mHandler.sendMessage(message);
-        }
-
-        public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
-            if (retval == Status.SUCCESS) {
-                logAndPrint(Log.INFO, pw, portName + " role switch successful");
-            } else {
-                logAndPrint(Log.ERROR, pw, portName + " role switch failed");
-            }
-        }
-    }
-
-    final class DeathRecipient implements HwBinder.DeathRecipient {
-        public IndentingPrintWriter pw;
-
-        DeathRecipient(IndentingPrintWriter pw) {
-            this.pw = pw;
-        }
-
-        @Override
-        public void serviceDied(long cookie) {
-            if (cookie == USB_HAL_DEATH_COOKIE) {
-                logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
-                synchronized (mLock) {
-                    mProxy = null;
-                }
-            }
-        }
-    }
-
-    final class ServiceNotification extends IServiceNotification.Stub {
-        @Override
-        public void onRegistration(String fqName, String name, boolean preexisting) {
-            logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
-            connectToProxy(null);
-        }
-    }
-
-    private void connectToProxy(IndentingPrintWriter pw) {
-        synchronized (mLock) {
-            if (mProxy != null) {
-                return;
-            }
-
-            try {
-                mProxy = IUsb.getService();
-                mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
-                mProxy.setCallback(mHALCallback);
-                mProxy.queryPortStatus();
-                updateUsbHalVersion();
-            } catch (NoSuchElementException e) {
-                logAndPrintException(pw, "connectToProxy: usb hal service not found."
-                        + " Did the service fail to start?", e);
-            } catch (RemoteException e) {
-                logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
-            }
-        }
-    }
-
     /**
      * Simulated ports directly add the new roles to mSimulatedPorts before calling.
      * USB hal callback populates and sends the newPortInfo.
@@ -869,7 +798,10 @@
                         portInfo.supportsEnableContaminantPresenceProtection,
                         portInfo.contaminantProtectionStatus,
                         portInfo.supportsEnableContaminantPresenceDetection,
-                        portInfo.contaminantDetectionStatus, pw);
+                        portInfo.contaminantDetectionStatus,
+                        portInfo.usbDataStatus,
+                        portInfo.powerTransferLimited,
+                        portInfo.powerBrickStatus, pw);
             }
         } else {
             for (RawPortInfo currentPortInfo : newPortInfo) {
@@ -881,7 +813,10 @@
                         currentPortInfo.supportsEnableContaminantPresenceProtection,
                         currentPortInfo.contaminantProtectionStatus,
                         currentPortInfo.supportsEnableContaminantPresenceDetection,
-                        currentPortInfo.contaminantDetectionStatus, pw);
+                        currentPortInfo.contaminantDetectionStatus,
+                        currentPortInfo.usbDataStatus,
+                        currentPortInfo.powerTransferLimited,
+                        currentPortInfo.powerBrickStatus, pw);
             }
         }
 
@@ -917,6 +852,9 @@
             int contaminantProtectionStatus,
             boolean supportsEnableContaminantPresenceDetection,
             int contaminantDetectionStatus,
+            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.
@@ -975,7 +913,8 @@
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
-                    contaminantDetectionStatus);
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus);
             mPorts.put(portId, portInfo);
         } else {
             // Validate that ports aren't changing definition out from under us.
@@ -1012,7 +951,8 @@
                     currentPowerRole, canChangePowerRole,
                     currentDataRole, canChangeDataRole,
                     supportedRoleCombinations, contaminantProtectionStatus,
-                    contaminantDetectionStatus)) {
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -1034,6 +974,7 @@
     private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
         logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
         enableContaminantDetectionIfNeeded(portInfo, pw);
+        disableLimitPowerTransferIfNeeded(portInfo, pw);
         handlePortLocked(portInfo, pw);
     }
 
@@ -1090,6 +1031,19 @@
         }
     }
 
+    private void disableLimitPowerTransferIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) {
+        if (!mConnected.containsKey(portInfo.mUsbPort.getId())) {
+            return;
+        }
+
+        if (mConnected.get(portInfo.mUsbPort.getId())
+                && !portInfo.mUsbPortStatus.isConnected()
+                && portInfo.mUsbPortStatus.isPowerTransferLimited()) {
+            // Relax enableLimitPowerTransfer upon unplug.
+            enableLimitPowerTransfer(portInfo.mUsbPort.getId(), false, ++mTransactionId, null, pw);
+        }
+    }
+
     private void logToStatsd(PortInfo portInfo, IndentingPrintWriter pw) {
         // Port is removed
         if (portInfo.mUsbPortStatus == null) {
@@ -1141,14 +1095,14 @@
         }
     }
 
-    private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+    public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
         Slog.println(priority, TAG, msg);
         if (pw != null) {
             pw.println(msg);
         }
     }
 
-    private static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+    public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
         Slog.e(TAG, msg, e);
         if (pw != null) {
             pw.println(msg + e);
@@ -1179,7 +1133,7 @@
     /**
      * Describes a USB port.
      */
-    private static final class PortInfo {
+    public static final class PortInfo {
         public static final int DISPOSITION_ADDED = 0;
         public static final int DISPOSITION_CHANGED = 1;
         public static final int DISPOSITION_READY = 2;
@@ -1224,7 +1178,9 @@
                     != supportedRoleCombinations) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
-                        UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED);
+                        UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED,
+                        new int[]{UsbPortStatus.USB_DATA_STATUS_UNKNOWN}, false,
+                        UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN);
                 dispositionChanged = true;
             }
 
@@ -1239,11 +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) {
+                int contaminantDetectionStatus, int[] usbDataStatus,
+                boolean powerTransferLimited, int powerBrickStatus) {
             boolean dispositionChanged = false;
 
             mCanChangeMode = canChangeMode;
@@ -1258,10 +1234,16 @@
                     || mUsbPortStatus.getContaminantProtectionStatus()
                     != contaminantProtectionStatus
                     || mUsbPortStatus.getContaminantDetectionStatus()
-                    != contaminantDetectionStatus) {
+                    != contaminantDetectionStatus
+                    || !dataStatusEquals(mUsbPortStatus.getUsbDataStatus(), usbDataStatus)
+                    || mUsbPortStatus.isPowerTransferLimited()
+                    != powerTransferLimited
+                    || mUsbPortStatus.getPowerBrickStatus()
+                    != powerBrickStatus) {
                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
                         supportedRoleCombinations, contaminantProtectionStatus,
-                        contaminantDetectionStatus);
+                        contaminantDetectionStatus, usbDataStatus,
+                        powerTransferLimited, powerBrickStatus);
                 dispositionChanged = true;
             }
 
@@ -1290,7 +1272,6 @@
                     UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis);
             dump.write("last_connect_duration_millis",
                     UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis);
-
             dump.end(token);
         }
 
@@ -1304,115 +1285,4 @@
                     + ", lastConnectDurationMillis=" + mLastConnectDurationMillis;
         }
     }
-
-    /**
-     * Used for storing the raw data from the kernel
-     * Values of the member variables mocked directly incase of emulation.
-     */
-    private static final class RawPortInfo implements Parcelable {
-        public final String portId;
-        public final int supportedModes;
-        public final int supportedContaminantProtectionModes;
-        public int currentMode;
-        public boolean canChangeMode;
-        public int currentPowerRole;
-        public boolean canChangePowerRole;
-        public int currentDataRole;
-        public boolean canChangeDataRole;
-        public boolean supportsEnableContaminantPresenceProtection;
-        public int contaminantProtectionStatus;
-        public boolean supportsEnableContaminantPresenceDetection;
-        public int contaminantDetectionStatus;
-
-        RawPortInfo(String portId, int supportedModes) {
-            this.portId = portId;
-            this.supportedModes = supportedModes;
-            this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
-            this.supportsEnableContaminantPresenceProtection = false;
-            this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
-            this.supportsEnableContaminantPresenceDetection = false;
-            this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
-        }
-
-        RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
-                int currentMode, boolean canChangeMode,
-                int currentPowerRole, boolean canChangePowerRole,
-                int currentDataRole, boolean canChangeDataRole,
-                boolean supportsEnableContaminantPresenceProtection,
-                int contaminantProtectionStatus,
-                boolean supportsEnableContaminantPresenceDetection,
-                int contaminantDetectionStatus) {
-            this.portId = portId;
-            this.supportedModes = supportedModes;
-            this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
-            this.currentMode = currentMode;
-            this.canChangeMode = canChangeMode;
-            this.currentPowerRole = currentPowerRole;
-            this.canChangePowerRole = canChangePowerRole;
-            this.currentDataRole = currentDataRole;
-            this.canChangeDataRole = canChangeDataRole;
-            this.supportsEnableContaminantPresenceProtection =
-                    supportsEnableContaminantPresenceProtection;
-            this.contaminantProtectionStatus = contaminantProtectionStatus;
-            this.supportsEnableContaminantPresenceDetection =
-                    supportsEnableContaminantPresenceDetection;
-            this.contaminantDetectionStatus = contaminantDetectionStatus;
-        }
-
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(portId);
-            dest.writeInt(supportedModes);
-            dest.writeInt(supportedContaminantProtectionModes);
-            dest.writeInt(currentMode);
-            dest.writeByte((byte) (canChangeMode ? 1 : 0));
-            dest.writeInt(currentPowerRole);
-            dest.writeByte((byte) (canChangePowerRole ? 1 : 0));
-            dest.writeInt(currentDataRole);
-            dest.writeByte((byte) (canChangeDataRole ? 1 : 0));
-            dest.writeBoolean(supportsEnableContaminantPresenceProtection);
-            dest.writeInt(contaminantProtectionStatus);
-            dest.writeBoolean(supportsEnableContaminantPresenceDetection);
-            dest.writeInt(contaminantDetectionStatus);
-        }
-
-        public static final Parcelable.Creator<RawPortInfo> CREATOR =
-                new Parcelable.Creator<RawPortInfo>() {
-            @Override
-            public RawPortInfo createFromParcel(Parcel in) {
-                String id = in.readString();
-                int supportedModes = in.readInt();
-                int supportedContaminantProtectionModes = in.readInt();
-                int currentMode = in.readInt();
-                boolean canChangeMode = in.readByte() != 0;
-                int currentPowerRole = in.readInt();
-                boolean canChangePowerRole = in.readByte() != 0;
-                int currentDataRole = in.readInt();
-                boolean canChangeDataRole = in.readByte() != 0;
-                boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
-                int contaminantProtectionStatus = in.readInt();
-                boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
-                int contaminantDetectionStatus = in.readInt();
-                return new RawPortInfo(id, supportedModes,
-                        supportedContaminantProtectionModes, currentMode, canChangeMode,
-                        currentPowerRole, canChangePowerRole,
-                        currentDataRole, canChangeDataRole,
-                        supportsEnableContaminantPresenceProtection,
-                        contaminantProtectionStatus,
-                        supportsEnableContaminantPresenceDetection,
-                        contaminantDetectionStatus);
-            }
-
-            @Override
-            public RawPortInfo[] newArray(int size) {
-                return new RawPortInfo[size];
-            }
-        };
-    }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 3d3538d..88ffc7d61 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.usb;
 
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
 import static android.hardware.usb.UsbPortStatus.MODE_DFP;
@@ -35,6 +36,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.usb.IUsbManager;
+import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
@@ -44,6 +46,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.usb.UsbServiceDumpProto;
@@ -731,6 +734,28 @@
     }
 
     @Override
+    public void enableLimitPowerTransfer(String portId, boolean limit, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portId, "portId must not be null. opID:" + operationId);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mPortManager != null) {
+                mPortManager.enableLimitPowerTransfer(portId, limit, operationId, callback, null);
+            } else {
+                try {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "enableLimitPowerTransfer: Failed to call onOperationComplete", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void enableContaminantDetection(String portId, boolean enable) {
         Objects.requireNonNull(portId, "portId must not be null");
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
@@ -762,15 +787,52 @@
     }
 
     @Override
-    public boolean enableUsbDataSignal(boolean enable) {
+    public boolean enableUsbData(String portId, boolean enable, int operationId,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
+                + operationId);
+        Objects.requireNonNull(callback, "enableUsbData: 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) {
-                return mPortManager.enableUsbDataSignal(enable);
+                wait = mPortManager.enableUsbData(portId, enable, operationId, callback, null);
             } else {
-                return false;
+                wait = false;
+                try {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return wait;
+    }
+
+    @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);
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
new file mode 100644
index 0000000..db0c80f
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
@@ -0,0 +1,469 @@
+/*
+ * 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.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbManager;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
+import com.android.server.usb.descriptors.UsbEndpointDescriptor;
+import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
+import com.android.server.usb.descriptors.UsbMidiBlockParser;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * A MIDI device that opens device connections to MIDI 2.0 endpoints.
+ */
+public final class UsbUniversalMidiDevice implements Closeable {
+    private static final String TAG = "UsbUniversalMidiDevice";
+    private static final boolean DEBUG = false;
+
+    private Context mContext;
+    private UsbDevice mUsbDevice;
+    private UsbDescriptorParser mParser;
+    private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
+
+    // USB outputs are MIDI inputs
+    private final InputReceiverProxy[] mMidiInputPortReceivers;
+    private final int mNumInputs;
+    private final int mNumOutputs;
+
+    private MidiDeviceServer mServer;
+
+    // event schedulers for each input port of the physical device
+    private MidiEventScheduler[] mEventSchedulers;
+
+    private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
+    private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
+    private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
+
+    private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
+    private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
+
+    private final Object mLock = new Object();
+    private boolean mIsOpen;
+
+    private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+
+        @Override
+        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+            MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+            int numInputPorts = deviceInfo.getInputPortCount();
+            int numOutputPorts = deviceInfo.getOutputPortCount();
+            boolean hasOpenPorts = false;
+
+            for (int i = 0; i < numInputPorts; i++) {
+                if (status.isInputPortOpen(i)) {
+                    hasOpenPorts = true;
+                    break;
+                }
+            }
+
+            if (!hasOpenPorts) {
+                for (int i = 0; i < numOutputPorts; i++) {
+                    if (status.getOutputPortOpenCount(i) > 0) {
+                        hasOpenPorts = true;
+                        break;
+                    }
+                }
+            }
+
+            synchronized (mLock) {
+                if (hasOpenPorts && !mIsOpen) {
+                    openLocked();
+                } else if (!hasOpenPorts && mIsOpen) {
+                    closeLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onClose() {
+        }
+    };
+
+    // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
+    // until the device has active clients
+    private static final class InputReceiverProxy extends MidiReceiver {
+        private MidiReceiver mReceiver;
+
+        @Override
+        public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+            MidiReceiver receiver = mReceiver;
+            if (receiver != null) {
+                receiver.send(msg, offset, count, timestamp);
+            }
+        }
+
+        public void setReceiver(MidiReceiver receiver) {
+            mReceiver = receiver;
+        }
+
+        @Override
+        public void onFlush() throws IOException {
+            MidiReceiver receiver = mReceiver;
+            if (receiver != null) {
+                receiver.flush();
+            }
+        }
+    }
+
+    /**
+     * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
+     * will be created individually as some devices don't have the same number of
+     * inputs and outputs.
+     */
+    public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice,
+            UsbDescriptorParser parser) {
+        UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser);
+        if (!midiDevice.register(context)) {
+            IoUtils.closeQuietly(midiDevice);
+            Log.e(TAG, "createDeviceServer failed");
+            return null;
+        }
+        return midiDevice;
+    }
+
+    private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
+        mUsbDevice = usbDevice;
+        mParser = parser;
+
+        mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+
+        int numInputs = 0;
+        int numOutputs = 0;
+
+        for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+            UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+            for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+                    endpointIndex++) {
+                UsbEndpointDescriptor endpoint =
+                        interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+                // 0 is output, 1 << 7 is input.
+                if (endpoint.getDirection() == 0) {
+                    numOutputs++;
+                } else {
+                    numInputs++;
+                }
+            }
+        }
+
+        mNumInputs = numInputs;
+        mNumOutputs = numOutputs;
+
+        Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
+                + numOutputs + " outputs");
+
+        // Create MIDI port receivers based on the number of output ports. The
+        // output of USB is the input of MIDI.
+        mMidiInputPortReceivers = new InputReceiverProxy[numOutputs];
+        for (int port = 0; port < numOutputs; port++) {
+            mMidiInputPortReceivers[port] = new InputReceiverProxy();
+        }
+    }
+
+    private int calculateDefaultMidiProtocol() {
+        UsbManager manager = mContext.getSystemService(UsbManager.class);
+
+        for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+            UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+            boolean doesInterfaceContainInput = false;
+            boolean doesInterfaceContainOutput = false;
+            for (int endpointIndex = 0; (endpointIndex < interfaceDescriptor.getNumEndpoints())
+                    && !(doesInterfaceContainInput && doesInterfaceContainOutput);
+                    endpointIndex++) {
+                UsbEndpointDescriptor endpoint =
+                        interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+                // 0 is output, 1 << 7 is input.
+                if (endpoint.getDirection() == 0) {
+                    doesInterfaceContainOutput = true;
+                } else {
+                    doesInterfaceContainInput = true;
+                }
+            }
+
+            // Intentionally open the device connection to query the default MIDI type for
+            // a connection with both the input and output set.
+            if (doesInterfaceContainInput
+                    && doesInterfaceContainOutput) {
+                UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
+                if (!connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true)) {
+                    Log.d(TAG, "Can't claim control interface");
+                    continue;
+                }
+                int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
+                        interfaceDescriptor.getInterfaceNumber(),
+                        interfaceDescriptor.getAlternateSetting());
+
+                connection.close();
+                return defaultMidiProtocol;
+            }
+        }
+
+        Log.d(TAG, "Cannot find interface with both input and output endpoints");
+        return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
+    }
+
+    private boolean openLocked() {
+        UsbManager manager = mContext.getSystemService(UsbManager.class);
+
+        mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
+        mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
+        mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
+
+        for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+            ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
+            ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
+            UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+            for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+                    endpointIndex++) {
+                UsbEndpointDescriptor endpoint =
+                        interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+                // 0 is output, 1 << 7 is input.
+                if (endpoint.getDirection() == 0) {
+                    outputEndpoints.add(endpoint.toAndroid(mParser));
+                } else {
+                    inputEndpoints.add(endpoint.toAndroid(mParser));
+                }
+            }
+            if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
+                UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
+                connection.setInterface(interfaceDescriptor.toAndroid(mParser));
+                connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
+                mUsbDeviceConnections.add(connection);
+                mInputUsbEndpoints.add(inputEndpoints);
+                mOutputUsbEndpoints.add(outputEndpoints);
+            }
+        }
+
+        mEventSchedulers = new MidiEventScheduler[mNumOutputs];
+
+        for (int i = 0; i < mNumOutputs; i++) {
+            MidiEventScheduler scheduler = new MidiEventScheduler();
+            mEventSchedulers[i] = scheduler;
+            mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver());
+        }
+
+        final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
+
+        // Create input thread for each input port of the physical device
+        int portNumber = 0;
+        for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
+                connectionIndex++) {
+            for (int endpointIndex = 0;
+                    endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
+                    endpointIndex++) {
+                final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
+                final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+                final int portF = portNumber;
+
+                new Thread("UsbUniversalMidiDevice input thread " + portF) {
+                    @Override
+                    public void run() {
+                        byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
+                        try {
+                            while (true) {
+                                // Record time of event immediately after waking.
+                                long timestamp = System.nanoTime();
+                                synchronized (mLock) {
+                                    if (!mIsOpen) break;
+
+                                    int nRead = connectionF.bulkTransfer(epF, inputBuffer,
+                                            inputBuffer.length, 0);
+
+                                    // For USB, each 32 bit word of a UMP is
+                                    // sent with the least significant byte first.
+                                    swapEndiannessPerWord(inputBuffer, inputBuffer.length);
+
+                                    if (nRead > 0) {
+                                        if (DEBUG) {
+                                            logByteArray("Input ", inputBuffer, 0,
+                                                    nRead);
+                                        }
+                                        outputReceivers[portF].send(inputBuffer, 0, nRead,
+                                                timestamp);
+                                    }
+                                }
+                            }
+                        } catch (IOException e) {
+                            Log.d(TAG, "reader thread exiting");
+                        }
+                        Log.d(TAG, "input thread exit");
+                    }
+                }.start();
+
+                portNumber++;
+            }
+        }
+
+        // Create output thread for each output port of the physical device
+        portNumber = 0;
+        for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
+                connectionIndex++) {
+            for (int endpointIndex = 0;
+                    endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
+                    endpointIndex++) {
+                final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
+                final UsbEndpoint epF =
+                        mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+                final int portF = portNumber;
+                final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
+
+                new Thread("UsbUniversalMidiDevice output thread " + portF) {
+                    @Override
+                    public void run() {
+                        while (true) {
+                            MidiEvent event;
+                            try {
+                                event = (MidiEvent) eventSchedulerF.waitNextEvent();
+                            } catch (InterruptedException e) {
+                                // try again
+                                continue;
+                            }
+                            if (event == null) {
+                                break;
+                            }
+
+                            // For USB, each 32 bit word of a UMP is
+                            // sent with the least significant byte first.
+                            swapEndiannessPerWord(event.data, event.count);
+
+                            if (DEBUG) {
+                                logByteArray("Output ", event.data, 0,
+                                        event.count);
+                            }
+                            connectionF.bulkTransfer(epF, event.data, event.count, 0);
+                            eventSchedulerF.addEventToPool(event);
+                        }
+                        Log.d(TAG, "output thread exit");
+                    }
+                }.start();
+
+                portNumber++;
+            }
+        }
+
+        mIsOpen = true;
+        return true;
+    }
+
+    private boolean register(Context context) {
+        mContext = context;
+        MidiManager midiManager = context.getSystemService(MidiManager.class);
+        if (midiManager == null) {
+            Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
+            return false;
+        }
+
+        mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+
+        Bundle properties = new Bundle();
+        String manufacturer = mUsbDevice.getManufacturerName();
+        String product = mUsbDevice.getProductName();
+        String version = mUsbDevice.getVersion();
+        String name;
+        if (manufacturer == null || manufacturer.isEmpty()) {
+            name = product;
+        } else if (product == null || product.isEmpty()) {
+            name = manufacturer;
+        } else {
+            name = manufacturer + " " + product + " MIDI 2.0";
+        }
+        properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
+        properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
+        properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
+        properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
+        properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
+                mUsbDevice.getSerialNumber());
+        properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, mUsbDevice);
+
+        mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs,
+                null, null, properties, MidiDeviceInfo.TYPE_USB, mDefaultMidiProtocol, mCallback);
+        if (mServer == null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void close() throws IOException {
+        synchronized (mLock) {
+            if (mIsOpen) {
+                closeLocked();
+            }
+        }
+
+        if (mServer != null) {
+            IoUtils.closeQuietly(mServer);
+        }
+    }
+
+    private void closeLocked() {
+        for (int i = 0; i < mEventSchedulers.length; i++) {
+            mMidiInputPortReceivers[i].setReceiver(null);
+            mEventSchedulers[i].close();
+        }
+        for (UsbDeviceConnection connection : mUsbDeviceConnections) {
+            connection.close();
+        }
+        mUsbDeviceConnections = null;
+        mInputUsbEndpoints = null;
+        mOutputUsbEndpoints = null;
+
+        mIsOpen = false;
+    }
+
+    private void swapEndiannessPerWord(byte[] array, int size) {
+        for (int i = 0; i + 3 < size; i += 4) {
+            byte tmp = array[i];
+            array[i] = array[i + 3];
+            array[i + 3] = tmp;
+            tmp = array[i + 1];
+            array[i + 1] = array[i + 2];
+            array[i + 2] = tmp;
+        }
+    }
+
+    private static void logByteArray(String prefix, byte[] value, int offset, int count) {
+        StringBuilder builder = new StringBuilder(prefix);
+        for (int i = offset; i < offset + count; i++) {
+            builder.append(String.format("0x%02X", value[i]));
+            if (i != value.length - 1) {
+                builder.append(", ");
+            }
+        }
+        Log.d(TAG, builder.toString());
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 409e605c..bfcf621 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,8 +38,8 @@
     static final byte ATTRIBSMASK_SYNC  = 0x0C;
     static final byte ATTRIBMASK_TRANS  = 0x03;
 
-    public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
-        super(length, type, subclass);
+    public UsbACAudioControlEndpoint(int length, byte type, int subclass, byte subtype) {
+        super(length, type, subclass, subtype);
     }
 
     public byte getAddress() {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index e63bb74..ae9ca0d 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,8 +24,8 @@
     private static final String TAG = "UsbACAudioStreamEndpoint";
 
     //TODO data fields...
-    public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
-        super(length, type, subclass);
+    public UsbACAudioStreamEndpoint(int length, byte type, int subclass, byte subtype) {
+        super(length, type, subclass, subtype);
     }
 
     @Override
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
index ff7f393..b7f9ac3 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,13 +25,17 @@
 abstract class UsbACEndpoint extends UsbDescriptor {
     private static final String TAG = "UsbACEndpoint";
 
+    public static final byte MS_GENERAL = 1;
+    public static final byte MS_GENERAL_2_0 = 2;
+
     protected final int mSubclass; // from the mSubclass member of the "enclosing"
                                    // Interface Descriptor, not the stream.
-    protected byte mSubtype;       // 2:1 HEADER descriptor subtype
+    protected final byte mSubtype;       // 2:1 HEADER descriptor subtype
 
-    UsbACEndpoint(int length, byte type, int subclass) {
+    UsbACEndpoint(int length, byte type, int subclass, byte subtype) {
         super(length, type);
         mSubclass = subclass;
+        mSubtype = subtype;
     }
 
     public int getSubclass() {
@@ -44,33 +48,39 @@
 
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        mSubtype = stream.getByte();
         return mLength;
     }
 
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
-                                                int length, byte type) {
+                                                int length, byte type, byte subType) {
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
         int subClass = interfaceDesc.getUsbSubclass();
-        // TODO shouldn't this switch on subtype?
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, "---> AUDIO_AUDIOCONTROL");
                 }
-                return new UsbACAudioControlEndpoint(length, type, subClass);
+                return new UsbACAudioControlEndpoint(length, type, subClass, subType);
 
             case AUDIO_AUDIOSTREAMING:
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, "---> AUDIO_AUDIOSTREAMING");
                 }
-                return new UsbACAudioStreamEndpoint(length, type, subClass);
+                return new UsbACAudioStreamEndpoint(length, type, subClass, subType);
 
             case AUDIO_MIDISTREAMING:
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, "---> AUDIO_MIDISTREAMING");
                 }
-                return new UsbACMidiEndpoint(length, type, subClass);
+                switch (subType) {
+                    case MS_GENERAL:
+                        return new UsbACMidi10Endpoint(length, type, subClass, subType);
+                    case MS_GENERAL_2_0:
+                        return new UsbACMidi20Endpoint(length, type, subClass, subType);
+                    default:
+                        Log.w(TAG, "Unknown Midi Endpoint id:0x" + Integer.toHexString(subType));
+                        return null;
+                }
 
             default:
                 Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass));
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java
similarity index 72%
rename from services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
rename to services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java
index 42ee889..49b9d7b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java
@@ -22,14 +22,14 @@
  * An audio class-specific Midi Endpoint.
  * see midi10.pdf section 6.2.2
  */
-public final class UsbACMidiEndpoint extends UsbACEndpoint {
-    private static final String TAG = "UsbACMidiEndpoint";
+public final class UsbACMidi10Endpoint extends UsbACEndpoint {
+    private static final String TAG = "UsbACMidi10Endpoint";
 
     private byte mNumJacks;
-    private byte[] mJackIds;
+    private byte[] mJackIds = new byte[0];
 
-    public UsbACMidiEndpoint(int length, byte type, int subclass) {
-        super(length, type, subclass);
+    public UsbACMidi10Endpoint(int length, byte type, int subclass, byte subtype) {
+        super(length, type, subclass, subtype);
     }
 
     public byte getNumJacks() {
@@ -45,9 +45,11 @@
         super.parseRawDescriptors(stream);
 
         mNumJacks = stream.getByte();
-        mJackIds = new byte[mNumJacks];
-        for (int jack = 0; jack < mNumJacks; jack++) {
-            mJackIds[jack] = stream.getByte();
+        if (mNumJacks > 0) {
+            mJackIds = new byte[mNumJacks];
+            for (int jack = 0; jack < mNumJacks; jack++) {
+                mJackIds[jack] = stream.getByte();
+            }
         }
         return mLength;
     }
@@ -56,10 +58,10 @@
     public void report(ReportCanvas canvas) {
         super.report(canvas);
 
-        canvas.writeHeader(3, "AC Midi Endpoint: " + ReportCanvas.getHexString(getType())
+        canvas.writeHeader(3, "ACMidi10Endpoint: " + ReportCanvas.getHexString(getType())
                 + " Length: " + getLength());
         canvas.openList();
         canvas.writeListItem("" + getNumJacks() + " Jacks.");
         canvas.closeList();
     }
-}
\ No newline at end of file
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java
new file mode 100644
index 0000000..1024a5b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 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.usb.descriptors;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * An audio class-specific Midi Endpoint.
+ * see midi10.pdf section 6.2.2
+ */
+public final class UsbACMidi20Endpoint extends UsbACEndpoint {
+    private static final String TAG = "UsbACMidi20Endpoint";
+
+    private byte mNumGroupTerminals;
+    private byte[] mBlockIds = new byte[0];
+
+    public UsbACMidi20Endpoint(int length, byte type, int subclass, byte subtype) {
+        super(length, type, subclass, subtype);
+    }
+
+    public byte getNumGroupTerminals() {
+        return mNumGroupTerminals;
+    }
+
+    public byte[] getBlockIds() {
+        return mBlockIds;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        mNumGroupTerminals = stream.getByte();
+        if (mNumGroupTerminals > 0) {
+            mBlockIds = new byte[mNumGroupTerminals];
+            for (int block = 0; block < mNumGroupTerminals; block++) {
+                mBlockIds[block] = stream.getByte();
+            }
+        }
+        return mLength;
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+
+        canvas.writeHeader(3, "AC Midi20 Endpoint: " + ReportCanvas.getHexString(getType())
+                + " Length: " + getLength());
+        canvas.openList();
+        canvas.writeListItem("" + getNumGroupTerminals() + " Group Terminals.");
+        canvas.closeList();
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 7250a07..3412a6f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -30,6 +30,9 @@
 
     private final String mDeviceAddr;
 
+    private static final int MS_MIDI_1_0 = 0x0100;
+    private static final int MS_MIDI_2_0 = 0x0200;
+
     // Descriptor Objects
     private static final int DESCRIPTORS_ALLOC_SIZE = 128;
     private final ArrayList<UsbDescriptor> mDescriptors;
@@ -215,6 +218,7 @@
                             Log.w(TAG, "  Unparsed Class-specific");
                             break;
                     }
+                    mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
                 }
                 break;
 
@@ -222,17 +226,25 @@
                 if (mCurInterfaceDescriptor != null) {
                     int subClass = mCurInterfaceDescriptor.getUsbClass();
                     switch (subClass) {
-                        case UsbDescriptor.CLASSID_AUDIO:
-                            descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
+                        case UsbDescriptor.CLASSID_AUDIO: {
+                            Byte subType = stream.getByte();
+                            if (DEBUG) {
+                                Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x"
+                                        + Integer.toHexString(type));
+                            }
+                            descriptor = UsbACEndpoint.allocDescriptor(this, length, type,
+                                    subType);
+                        }
                             break;
 
                         case UsbDescriptor.CLASSID_VIDEO: {
-                            Byte subtype = stream.getByte();
+                            Byte subType = stream.getByte();
                             if (DEBUG) {
                                 Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x"
                                         + Integer.toHexString(type));
                             }
-                            descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype);
+                            descriptor = UsbVCEndpoint.allocDescriptor(this, length, type,
+                                    subType);
                         }
                             break;
 
@@ -644,8 +656,8 @@
         for (UsbDescriptor descriptor : descriptors) {
             // enusure that this isn't an unrecognized interface descriptor
             if (descriptor instanceof UsbInterfaceDescriptor) {
-                UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor;
-                if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+                UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+                if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
                     return true;
                 }
             } else {
@@ -656,17 +668,90 @@
         return false;
     }
 
-    private int calculateNumMidiPorts(boolean isOutput) {
+    /**
+     * @hide
+     */
+    public boolean containsUniversalMidiDeviceEndpoint() {
+        ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
+                findUniversalMidiInterfaceDescriptors();
+        int outputCount = 0;
+        int inputCount = 0;
+        for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
+                interfaceIndex++) {
+            UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex);
+            for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+                    endpointIndex++) {
+                UsbEndpointDescriptor endpoint =
+                        interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+                // 0 is output, 1 << 7 is input.
+                if (endpoint.getDirection() == 0) {
+                    outputCount++;
+                } else {
+                    inputCount++;
+                }
+            }
+        }
+        return (outputCount > 0) || (inputCount > 0);
+    }
+
+    /**
+     * @hide
+     */
+    public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
+        int count = 0;
+        ArrayList<UsbDescriptor> descriptors =
+                getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
+        ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
+                new ArrayList<UsbInterfaceDescriptor>();
+
+        for (UsbDescriptor descriptor : descriptors) {
+            // ensure that this isn't an unrecognized interface descriptor
+            if (descriptor instanceof UsbInterfaceDescriptor) {
+                UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+                if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+                    UsbDescriptor classSpecificDescriptor =
+                            interfaceDescriptor.getClassSpecificInterfaceDescriptor();
+                    if (classSpecificDescriptor != null) {
+                        if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+                            UsbMSMidiHeader midiHeader =
+                                    (UsbMSMidiHeader) classSpecificDescriptor;
+                            if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
+                                universalMidiInterfaces.add(interfaceDescriptor);
+                            }
+                        }
+                    }
+                }
+            } else {
+                Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
+                        + " t:0x" + Integer.toHexString(descriptor.getType()));
+            }
+        }
+        return universalMidiInterfaces;
+    }
+
+    private int calculateNumLegacyMidiPorts(boolean isOutput) {
         int count = 0;
         ArrayList<UsbDescriptor> descriptors =
                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
         for (UsbDescriptor descriptor : descriptors) {
             // ensure that this isn't an unrecognized interface descriptor
             if (descriptor instanceof UsbInterfaceDescriptor) {
-                UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor;
-                if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
-                    for (int i = 0; i < interfaceDescr.getNumEndpoints(); i++) {
-                        UsbEndpointDescriptor endpoint = interfaceDescr.getEndpointDescriptor(i);
+                UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+                if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+                    UsbDescriptor classSpecificDescriptor =
+                            interfaceDescriptor.getClassSpecificInterfaceDescriptor();
+                    if (classSpecificDescriptor != null) {
+                        if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+                            UsbMSMidiHeader midiHeader =
+                                    (UsbMSMidiHeader) classSpecificDescriptor;
+                            if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) {
+                                continue;
+                            }
+                        }
+                    }
+                    for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
+                        UsbEndpointDescriptor endpoint =
+                                interfaceDescriptor.getEndpointDescriptor(i);
                         // 0 is output, 1 << 7 is input.
                         if ((endpoint.getDirection() == 0) == isOutput) {
                             count++;
@@ -684,15 +769,15 @@
     /**
      * @hide
      */
-    public int calculateNumMidiInputs() {
-        return calculateNumMidiPorts(false /*isOutput*/);
+    public int calculateNumLegacyMidiInputs() {
+        return calculateNumLegacyMidiPorts(false /*isOutput*/);
     }
 
     /**
      * @hide
      */
-    public int calculateNumMidiOutputs() {
-        return calculateNumMidiPorts(true /*isOutput*/);
+    public int calculateNumLegacyMidiOutputs() {
+        return calculateNumLegacyMidiPorts(true /*isOutput*/);
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 4d0cfea..ab07ce7 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -112,7 +112,10 @@
         return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
     }
 
-    /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+    /**
+    * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing.
+    */
+    public UsbEndpoint toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "toAndroid() type:"
                     + Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE)
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 64dbd97..ca4613b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -42,6 +42,8 @@
     private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
             new ArrayList<UsbEndpointDescriptor>();
 
+    private UsbDescriptor mClassSpecificInterfaceDescriptor;
+
     UsbInterfaceDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 3;
@@ -105,7 +107,18 @@
         mEndpointDescriptors.add(endpoint);
     }
 
-    UsbInterface toAndroid(UsbDescriptorParser parser) {
+    public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
+        mClassSpecificInterfaceDescriptor = descriptor;
+    }
+
+    public UsbDescriptor getClassSpecificInterfaceDescriptor() {
+        return mClassSpecificInterfaceDescriptor;
+    }
+
+    /**
+    * Returns a UsbInterface that this UsbInterfaceDescriptor is describing.
+    */
+    public UsbInterface toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass)
                     + " subclass:" + Integer.toHexString(mUsbSubclass)
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index d0ca6db..7653561 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,13 +25,19 @@
 public final class UsbMSMidiHeader extends UsbACInterface {
     private static final String TAG = "UsbMSMidiHeader";
 
+    private int mMidiStreamingClass;  // MSC Specification Release (BCD).
+
     public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
+    public int getMidiStreamingClass() {
+        return mMidiStreamingClass;
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        // TODO - read data memebers
+        mMidiStreamingClass = stream.unpackUsbShort();
         stream.advance(mLength - stream.getReadCount());
         return mLength;
     }
@@ -42,6 +48,7 @@
 
         canvas.writeHeader(3, "MS Midi Header: " + ReportCanvas.getHexString(getType())
                 + " SubType: " + ReportCanvas.getHexString(getSubclass())
-                + " Length: " + getLength());
+                + " Length: " + getLength()
+                + " MidiStreamingClass :" + getMidiStreamingClass());
     }
 }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java
new file mode 100644
index 0000000..37bd0f8f
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 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.usb.descriptors;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDeviceConnection;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ * A class to parse Block Descriptors
+ * see midi20.pdf section 5.4
+ */
+public class UsbMidiBlockParser {
+    private static final String TAG = "UsbMidiBlockParser";
+
+    // Block header size
+    public static final int MIDI_BLOCK_HEADER_SIZE = 5;
+    public static final int MIDI_BLOCK_SIZE = 13;
+    public static final int REQ_GET_DESCRIPTOR = 0x06;
+    public static final int CS_GR_TRM_BLOCK = 0x26;     // Class-specific GR_TRM_BLK
+    public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header
+    public static final int REQ_TIMEOUT_MS = 2000;      // 2 second timeout
+    public static final int DEFAULT_MIDI_TYPE = 1;      // Default MIDI type
+
+    protected int mHeaderLength;            // 0:1 Size of header descriptor
+    protected int mHeaderDescriptorType;    // 1:1 Descriptor Type
+    protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype
+    protected int mTotalLength;             // 3:2 Total Length of header and blocks
+
+    static class GroupTerminalBlock {
+        protected int mLength;                  // 0:1 Size of descriptor
+        protected int mDescriptorType;          // 1:1 Descriptor Type
+        protected int mDescriptorSubtype;       // 2:1 Descriptor Subtype
+        protected int mGroupBlockId;            // 3:1 Id of Group Terminal Block
+        protected int mGroupTerminalBlockType;  // 4:1 bi-directional, IN, or OUT
+        protected int mGroupTerminal;           // 5:1 Group Terminal Number
+        protected int mNumGroupTerminals;       // 6:1 Number of Group Terminals
+        protected int mBlockItem;               // 7:1 ID of STRING descriptor of Block item
+        protected int mMidiProtocol;            // 8:1 MIDI protocol
+        protected int mMaxInputBandwidth;       // 9:2 Max Input Bandwidth
+        protected int mMaxOutputBandwidth;      // 11:2 Max Output Bandwidth
+
+        public int parseRawDescriptors(ByteStream stream) {
+            mLength = stream.getUnsignedByte();
+            mDescriptorType = stream.getUnsignedByte();
+            mDescriptorSubtype = stream.getUnsignedByte();
+            mGroupBlockId = stream.getUnsignedByte();
+            mGroupTerminalBlockType = stream.getUnsignedByte();
+            mGroupTerminal = stream.getUnsignedByte();
+            mNumGroupTerminals = stream.getUnsignedByte();
+            mBlockItem = stream.getUnsignedByte();
+            mMidiProtocol = stream.getUnsignedByte();
+            mMaxInputBandwidth = stream.unpackUsbShort();
+            mMaxOutputBandwidth = stream.unpackUsbShort();
+            return mLength;
+        }
+    }
+
+    private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks =
+            new ArrayList<GroupTerminalBlock>();
+
+    public UsbMidiBlockParser() {
+    }
+
+    /**
+     * Parses a raw ByteStream into a block terminal descriptor.
+     * The header is parsed before each block is parsed.
+     * @param   stream  ByteStream to parse
+     * @return          The total length that has been parsed.
+     */
+    public int parseRawDescriptors(ByteStream stream) {
+        mHeaderLength = stream.getUnsignedByte();
+        mHeaderDescriptorType = stream.getUnsignedByte();
+        mHeaderDescriptorSubtype = stream.getUnsignedByte();
+        mTotalLength = stream.unpackUsbShort();
+
+        while (stream.available() >= MIDI_BLOCK_SIZE) {
+            GroupTerminalBlock block = new GroupTerminalBlock();
+            block.parseRawDescriptors(stream);
+            mGroupTerminalBlocks.add(block);
+        }
+
+        return mTotalLength;
+    }
+
+    /**
+     * Calculates the MIDI type through querying the device twice, once for the size
+     * of the block descriptor and once for the block descriptor. This descriptor is
+     * then parsed to return the MIDI type.
+     * See the MIDI 2.0 USB doc for more info.
+     * @param  connection               UsbDeviceConnection to send the request
+     * @param  interfaceNumber          The interface number to query
+     * @param  alternateInterfaceNumber The alternate interface of the interface
+     * @return                          The MIDI type as an int.
+     */
+    public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber,
+            int alternateInterfaceNumber) {
+        byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE];
+        try {
+            // This first request is simply to get the full size of the descriptor.
+            // This info is stored in the last two bytes of the header.
+            int rdo = connection.controlTransfer(
+                    UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
+                            | UsbConstants.USB_CLASS_AUDIO,
+                    REQ_GET_DESCRIPTOR,
+                    (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
+                    interfaceNumber,
+                    byteArray,
+                    MIDI_BLOCK_HEADER_SIZE,
+                    REQ_TIMEOUT_MS);
+            if (rdo > 0) {
+                if (byteArray[1] != CS_GR_TRM_BLOCK) {
+                    Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]);
+                    return DEFAULT_MIDI_TYPE;
+                }
+                if (byteArray[2] != GR_TRM_BLOCK_HEADER) {
+                    Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]);
+                    return DEFAULT_MIDI_TYPE;
+                }
+                int newSize = (((int) byteArray[3]) & (0xff))
+                        + ((((int) byteArray[4]) & (0xff)) << 8);
+                if (newSize <= 0) {
+                    Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize);
+                    return DEFAULT_MIDI_TYPE;
+                }
+                byteArray = new byte[newSize];
+                rdo = connection.controlTransfer(
+                        UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
+                                | UsbConstants.USB_CLASS_AUDIO,
+                        REQ_GET_DESCRIPTOR,
+                        (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
+                        interfaceNumber,
+                        byteArray,
+                        newSize,
+                        REQ_TIMEOUT_MS);
+                if (rdo > 0) {
+                    ByteStream stream = new ByteStream(byteArray);
+                    parseRawDescriptors(stream);
+                    if (mGroupTerminalBlocks.isEmpty()) {
+                        Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE);
+                        return DEFAULT_MIDI_TYPE;
+                    } else {
+                        Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol);
+                        return mGroupTerminalBlocks.get(0).mMidiProtocol;
+                    }
+                } else {
+                    Log.e(TAG, "second transfer failed: " + rdo);
+                }
+            } else {
+                Log.e(TAG, "first transfer failed: " + rdo);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Can not communicate with USB device", e);
+        }
+        return DEFAULT_MIDI_TYPE;
+    }
+}
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
new file mode 100644
index 0000000..dd25620
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java
@@ -0,0 +1,151 @@
+/*
+ * 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.usb.hal.port;
+
+import android.hardware.usb.UsbPortStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Used for storing the raw data from the HAL.
+ * Values of the member variables mocked directly in case of emulation.
+ */
+public final class RawPortInfo implements Parcelable {
+    public final String portId;
+    public final int supportedModes;
+    public final int supportedContaminantProtectionModes;
+    public int currentMode;
+    public boolean canChangeMode;
+    public int currentPowerRole;
+    public boolean canChangePowerRole;
+    public int currentDataRole;
+    public boolean canChangeDataRole;
+    public boolean supportsEnableContaminantPresenceProtection;
+    public int contaminantProtectionStatus;
+    public boolean supportsEnableContaminantPresenceDetection;
+    public int contaminantDetectionStatus;
+    public int[] usbDataStatus;
+    public boolean powerTransferLimited;
+    public int powerBrickStatus;
+
+    public RawPortInfo(String portId, int supportedModes) {
+        this.portId = portId;
+        this.supportedModes = supportedModes;
+        this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+        this.supportsEnableContaminantPresenceProtection = false;
+        this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+        this.supportsEnableContaminantPresenceDetection = false;
+        this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+        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,
+            int currentMode, boolean canChangeMode,
+            int currentPowerRole, boolean canChangePowerRole,
+            int currentDataRole, boolean canChangeDataRole,
+            boolean supportsEnableContaminantPresenceProtection,
+            int contaminantProtectionStatus,
+            boolean supportsEnableContaminantPresenceDetection,
+            int contaminantDetectionStatus,
+            int[] usbDataStatus,
+            boolean powerTransferLimited,
+            int powerBrickStatus) {
+        this.portId = portId;
+        this.supportedModes = supportedModes;
+        this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
+        this.currentMode = currentMode;
+        this.canChangeMode = canChangeMode;
+        this.currentPowerRole = currentPowerRole;
+        this.canChangePowerRole = canChangePowerRole;
+        this.currentDataRole = currentDataRole;
+        this.canChangeDataRole = canChangeDataRole;
+        this.supportsEnableContaminantPresenceProtection =
+                supportsEnableContaminantPresenceProtection;
+        this.contaminantProtectionStatus = contaminantProtectionStatus;
+        this.supportsEnableContaminantPresenceDetection =
+                supportsEnableContaminantPresenceDetection;
+        this.contaminantDetectionStatus = contaminantDetectionStatus;
+        this.usbDataStatus = usbDataStatus;
+        this.powerTransferLimited = powerTransferLimited;
+        this.powerBrickStatus = powerBrickStatus;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(portId);
+        dest.writeInt(supportedModes);
+        dest.writeInt(supportedContaminantProtectionModes);
+        dest.writeInt(currentMode);
+        dest.writeByte((byte) (canChangeMode ? 1 : 0));
+        dest.writeInt(currentPowerRole);
+        dest.writeByte((byte) (canChangePowerRole ? 1 : 0));
+        dest.writeInt(currentDataRole);
+        dest.writeByte((byte) (canChangeDataRole ? 1 : 0));
+        dest.writeBoolean(supportsEnableContaminantPresenceProtection);
+        dest.writeInt(contaminantProtectionStatus);
+        dest.writeBoolean(supportsEnableContaminantPresenceDetection);
+        dest.writeInt(contaminantDetectionStatus);
+        dest.writeInt(usbDataStatus.length);
+        dest.writeIntArray(usbDataStatus);
+        dest.writeBoolean(powerTransferLimited);
+        dest.writeInt(powerBrickStatus);
+    }
+
+    public static final Parcelable.Creator<RawPortInfo> CREATOR =
+            new Parcelable.Creator<RawPortInfo>() {
+        @Override
+        public RawPortInfo createFromParcel(Parcel in) {
+            String id = in.readString();
+            int supportedModes = in.readInt();
+            int supportedContaminantProtectionModes = in.readInt();
+            int currentMode = in.readInt();
+            boolean canChangeMode = in.readByte() != 0;
+            int currentPowerRole = in.readInt();
+            boolean canChangePowerRole = in.readByte() != 0;
+            int currentDataRole = in.readInt();
+            boolean canChangeDataRole = in.readByte() != 0;
+            boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
+            int contaminantProtectionStatus = in.readInt();
+            boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
+            int contaminantDetectionStatus = in.readInt();
+            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,
+                    currentDataRole, canChangeDataRole,
+                    supportsEnableContaminantPresenceProtection,
+                    contaminantProtectionStatus,
+                    supportsEnableContaminantPresenceDetection,
+                    contaminantDetectionStatus, usbDataStatus,
+                    powerTransferLimited, powerBrickStatus);
+        }
+
+        @Override
+        public RawPortInfo[] newArray(int size) {
+            return new RawPortInfo[size];
+        }
+    };
+}
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
new file mode 100644
index 0000000..5582600
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -0,0 +1,609 @@
+/*
+ * 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.usb.hal.port;
+
+import static android.hardware.usb.UsbManager.USB_HAL_V2_0;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+import static com.android.server.usb.UsbPortManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.ContaminantProtectionStatus;
+import android.hardware.usb.IUsb;
+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.PortMode;
+import android.hardware.usb.Status;
+import android.hardware.usb.IUsbCallback;
+import android.hardware.usb.PortRole;
+import android.hardware.usb.PortStatus;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbPortManager;
+import com.android.server.usb.hal.port.RawPortInfo;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbPortAidl implements UsbPortHal {
+    private static final String TAG = UsbPortAidl.class.getSimpleName();
+    private static final String USB_AIDL_SERVICE =
+            "android.hardware.usb.IUsb/default";
+    private static final LongSparseArray<IUsbOperationInternal>
+                sCallbacks = new LongSparseArray<>();
+    // Proxy object for the usb hal daemon.
+    @GuardedBy("mLock")
+    private IUsb mProxy;
+    private UsbPortManager mPortManager;
+    public IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mLock = new Object();
+    // Callback when the UsbPort status is changed by the kernel.
+    private HALCallback mHALCallback;
+    private IBinder mBinder;
+    private boolean mSystemReady;
+    private long mTransactionId;
+
+    public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                throw new RemoteException("IUsb not initialized yet");
+            }
+        }
+        logAndPrint(Log.INFO, null, "USB HAL AIDL version: USB_HAL_V2_0");
+        return USB_HAL_V2_0;
+    }
+
+    @Override
+    public void systemReady() {
+        mSystemReady = true;
+    }
+
+    public void serviceDied() {
+        logAndPrint(Log.ERROR, mPw, "Usb AIDL hal service died");
+        synchronized (mLock) {
+            mProxy = null;
+        }
+        connectToProxy(null);
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            if (mProxy != null) {
+                return;
+            }
+
+            try {
+                mBinder = ServiceManager.waitForService(USB_AIDL_SERVICE);
+                mProxy = IUsb.Stub.asInterface(mBinder);
+                mBinder.linkToDeath(this::serviceDied, 0);
+                mProxy.setCallback(mHALCallback);
+                mProxy.queryPortStatus(++mTransactionId);
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb hal service not found."
+                        + " Did the service fail to start?", e);
+            } catch (RemoteException e) {
+                logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
+            }
+        }
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            return ServiceManager.isDeclared(USB_AIDL_SERVICE);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb Aidl hal service not found.", e);
+        }
+
+        return false;
+    }
+
+    public UsbPortAidl(UsbPortManager portManager, IndentingPrintWriter pw) {
+        mPortManager = Objects.requireNonNull(portManager);
+        mPw = pw;
+        mHALCallback = new HALCallback(null, mPortManager, this);
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void enableContaminantPresenceDetection(String portName, boolean enable,
+            long operationID) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID: "
+                        + operationID);
+                return;
+            }
+
+            try {
+                // Oneway call into the hal. Use the castFrom method from HIDL.
+                mProxy.enableContaminantPresenceDetection(portName, enable, operationID);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set contaminant detection. opID:"
+                        + operationID, e);
+            }
+        }
+    }
+
+    @Override
+    public void queryPortStatus(long operationID) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:"
+                        + operationID);
+                return;
+            }
+
+            try {
+                mProxy.queryPortStatus(operationID);
+            } catch (RemoteException e) {
+                logAndPrintException(null, "ServiceStart: Failed to query port status. opID:"
+                        + operationID, e);
+            }
+       }
+    }
+
+    @Override
+    public void switchMode(String portId, @HalUsbPortMode int newMode, long operationID) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:"
+                        + operationID);
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.setMode((byte)newMode);
+            try {
+                mProxy.switchRole(portId, newRole, operationID);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB port mode: "
+                        + "portId=" + portId
+                        + ", newMode=" + UsbPort.modeToString(newMode)
+                        + "opID:" + operationID, e);
+            }
+        }
+    }
+
+    @Override
+    public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole,
+            long operationID) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:"
+                        + operationID);
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.setPowerRole((byte)newPowerRole);
+            try {
+                mProxy.switchRole(portId, newRole, operationID);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId
+                        + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
+                        + "opID:" + operationID, e);
+            }
+        }
+    }
+
+    @Override
+    public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long operationID) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:"
+                        + operationID);
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.setDataRole((byte)newDataRole);
+            try {
+                mProxy.switchRole(portId, newRole, operationID);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId
+                        + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)
+                        + "opID:" + operationID, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean enableUsbData(String portName, boolean enable, long operationID,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portName);
+        Objects.requireNonNull(callback);
+        long key = operationID;
+        synchronized (mLock) {
+            try {
+                if (mProxy == null) {
+                    logAndPrint(Log.ERROR, mPw,
+                            "enableUsbData: Proxy is null. Retry !opID:"
+                            + operationID);
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    return false;
+                }
+                while (sCallbacks.get(key) != null) {
+                    key = ThreadLocalRandom.current().nextInt();
+                }
+                if (key != operationID) {
+                    logAndPrint(Log.INFO, mPw, "enableUsbData: operationID exists ! opID:"
+                            + operationID + " key:" + key);
+                }
+                try {
+                    sCallbacks.put(key, callback);
+                    mProxy.enableUsbData(portName, enable, key);
+                } catch (RemoteException e) {
+                    logAndPrintException(mPw,
+                            "enableUsbData: Failed to invoke enableUsbData: portID="
+                            + portName + "opID:" + operationID, e);
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    sCallbacks.remove(key);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "enableUsbData: Failed to call onOperationComplete portID="
+                        + portName + "opID:" + operationID, e);
+                sCallbacks.remove(key);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    @Override
+    public void enableLimitPowerTransfer(String portName, boolean limit, long operationID,
+            IUsbOperationInternal callback) {
+        Objects.requireNonNull(portName);
+        long key = operationID;
+        synchronized (mLock) {
+            try {
+                if (mProxy == null) {
+                    logAndPrint(Log.ERROR, mPw,
+                            "enableLimitPowerTransfer: 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, "enableUsbData: operationID exists ! opID:"
+                            + operationID + " key:" + key);
+                }
+                try {
+                    sCallbacks.put(key, callback);
+                    mProxy.limitPowerTransfer(portName, limit, key);
+                } catch (RemoteException e) {
+                    logAndPrintException(mPw,
+                            "enableLimitPowerTransfer: Failed while invoking AIDL HAL"
+                            + " portID=" + portName + " opID:" + operationID, e);
+                    if (callback != null) {
+                        callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                    }
+                    sCallbacks.remove(key);
+                }
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "enableLimitPowerTransfer: Failed to call onOperationComplete portID="
+                        + portName + " opID:" + operationID, e);
+            }
+        }
+    }
+
+    @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;
+        public UsbPortAidl mUsbPortAidl;
+
+        HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortAidl usbPortAidl) {
+            this.mPw = pw;
+            this.mPortManager = portManager;
+            this.mUsbPortAidl = usbPortAidl;
+        }
+
+        /**
+         * Converts from AIDL defined mode constants to UsbPortStatus constants.
+         * AIDL does not gracefully support bitfield when combined with enums.
+         */
+        private int toPortMode(byte aidlPortMode) {
+            switch (aidlPortMode) {
+                case PortMode.NONE:
+                    return UsbPortStatus.MODE_NONE;
+                case PortMode.UFP:
+                    return UsbPortStatus.MODE_UFP;
+                case PortMode.DFP:
+                    return UsbPortStatus.MODE_DFP;
+                case PortMode.DRP:
+                    return UsbPortStatus.MODE_DUAL;
+                case PortMode.AUDIO_ACCESSORY:
+                    return UsbPortStatus.MODE_AUDIO_ACCESSORY;
+                case PortMode.DEBUG_ACCESSORY:
+                    return UsbPortStatus.MODE_DEBUG_ACCESSORY;
+                default:
+                    UsbPortManager.logAndPrint(Log.ERROR, mPw, "Unrecognized aidlPortMode:"
+                            + aidlPortMode);
+                    return UsbPortStatus.MODE_NONE;
+            }
+        }
+
+        private int toSupportedModes(byte[] aidlPortModes) {
+            int supportedModes = UsbPortStatus.MODE_NONE;
+
+            for (byte aidlPortMode : aidlPortModes) {
+                supportedModes |= toPortMode(aidlPortMode);
+            }
+
+            return supportedModes;
+        }
+
+        /**
+         * Converts from AIDL defined contaminant protection constants to UsbPortStatus constants.
+         * AIDL does not gracefully support bitfield when combined with enums.
+         * Common to both ContaminantProtectionMode and ContaminantProtectionStatus.
+         */
+        private int toContaminantProtectionStatus(byte aidlContaminantProtection) {
+            switch (aidlContaminantProtection) {
+                case ContaminantProtectionStatus.NONE:
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+                case ContaminantProtectionStatus.FORCE_SINK:
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_SINK;
+                case ContaminantProtectionStatus.FORCE_SOURCE:
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_SOURCE;
+                case ContaminantProtectionStatus.FORCE_DISABLE:
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_FORCE_DISABLE;
+                case ContaminantProtectionStatus.DISABLED:
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_DISABLED;
+                default:
+                    UsbPortManager.logAndPrint(Log.ERROR, mPw,
+                            "Unrecognized aidlContaminantProtection:"
+                            + aidlContaminantProtection);
+                    return UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+            }
+        }
+
+        private int toSupportedContaminantProtectionModes(byte[] aidlModes) {
+            int supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+
+            for (byte aidlMode : aidlModes) {
+                supportedContaminantProtectionModes |= toContaminantProtectionStatus(aidlMode);
+            }
+
+            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) {
+            if (!mUsbPortAidl.mSystemReady) {
+                return;
+            }
+
+            if (retval != Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+            int numStatus = currentPortStatus.length;
+            for (int i = 0; i < numStatus; i++) {
+                PortStatus current = currentPortStatus[i];
+                RawPortInfo temp = new RawPortInfo(current.portName,
+                        toSupportedModes(current.supportedModes),
+                        toSupportedContaminantProtectionModes(current
+                                .supportedContaminantProtectionModes),
+                        toPortMode(current.currentMode),
+                        current.canChangeMode,
+                        current.currentPowerRole,
+                        current.canChangePowerRole,
+                        current.currentDataRole,
+                        current.canChangeDataRole,
+                        current.supportsEnableContaminantPresenceProtection,
+                        toContaminantProtectionStatus(current.contaminantProtectionStatus),
+                        current.supportsEnableContaminantPresenceDetection,
+                        current.contaminantDetectionStatus,
+                        toIntArray(current.usbDataStatus),
+                        current.powerTransferLimited,
+                        current.powerBrickStatus);
+                newPortInfo.add(temp);
+                UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: "
+                        + current.portName);
+            }
+            mPortManager.updatePorts(newPortInfo);
+        }
+
+        @Override
+        public void notifyRoleSwitchStatus(String portName, PortRole role, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, portName
+                        + " role switch successful. opID:"
+                        + operationID);
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed. err:"
+                        + retval
+                        + "opID:" + operationID);
+            }
+        }
+
+        @Override
+        public void notifyQueryPortStatus(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 + ": opID:"
+                        + operationID + " failed. err:" + retval);
+            }
+        }
+
+        @Override
+        public void notifyEnableUsbDataStatus(String portName, boolean enable, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyEnableUsbDataStatus:"
+                        + portName + ": opID:"
+                        + operationID + " enable:" + enable);
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName
+                        + "notifyEnableUsbDataStatus: opID:"
+                        + operationID + " failed. err:" + retval);
+            }
+            try {
+                sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS
+                        ? USB_OPERATION_SUCCESS
+                        : USB_OPERATION_ERROR_INTERNAL);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw,
+                        "notifyEnableUsbDataStatus: Failed to call onOperationComplete",
+                        e);
+            }
+        }
+
+        @Override
+        public void notifyContaminantEnabledStatus(String portName, boolean enable, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyContaminantEnabledStatus:"
+                        + portName + ": opID:"
+                        + operationID + " enable:" + enable);
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName
+                        + "notifyContaminantEnabledStatus: opID:"
+                        + operationID + " failed. err:" + retval);
+            }
+        }
+
+        @Override
+        public void notifyLimitPowerTransferStatus(String portName, boolean limit, int retval,
+                long operationID) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:"
+                        + operationID + " successful");
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName
+                        + "notifyLimitPowerTransferStatus: 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,
+                        "enableLimitPowerTransfer: Failed to call onOperationComplete",
+                        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
new file mode 100644
index 0000000..abfdd6f
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java
@@ -0,0 +1,196 @@
+/*
+ * 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.usb.hal.port;
+
+import android.annotation.IntDef;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbPortHal {
+    /**
+     * Power role: This USB port can act as a source (provide power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+    /**
+     * Power role: This USB port can act as a sink (receive power).
+     * @hide
+     */
+    public static final int HAL_POWER_ROLE_SINK = 2;
+
+    @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+            HAL_POWER_ROLE_SOURCE,
+            HAL_POWER_ROLE_SINK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPowerRole{}
+
+    /**
+     * Data role: This USB port can act as a host (access data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_HOST = 1;
+
+    /**
+     * Data role: This USB port can act as a device (offer data services).
+     * @hide
+     */
+    public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+    @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+            HAL_DATA_ROLE_HOST,
+            HAL_DATA_ROLE_DEVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbDataRole{}
+
+    /**
+     * This USB port can act as a downstream facing port (host).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_DFP = 1;
+
+    /**
+     * This USB port can act as an upstream facing port (device).
+     *
+     * @hide
+     */
+    public static final int HAL_MODE_UFP = 2;
+    @IntDef(prefix = { "HAL_MODE_" }, value = {
+            HAL_MODE_DFP,
+            HAL_MODE_UFP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface HalUsbPortMode{}
+
+    /**
+     * UsbPortManager would call this when the system is done booting.
+     */
+    public void systemReady();
+
+    /**
+     * Invoked to enable/disable contaminant presence detection on the USB port.
+     *
+     * @param portName Port Identifier.
+     * @param enable Enable contaminant presence detection when true.
+     *               Disable when false.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void enableContaminantPresenceDetection(String portName, boolean enable,
+            long transactionId);
+
+    /**
+     * Invoked to query port status of all the ports.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void queryPortStatus(long transactionId);
+
+    /**
+     * Invoked to switch USB port mode.
+     *
+     * @param portName Port Identifier.
+     * @param mode New mode that the port is switching into.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void switchMode(String portName, @HalUsbPortMode int mode, long transactionId);
+
+    /**
+     * Invoked to switch USB port power role.
+     *
+     * @param portName Port Identifier.
+     * @param powerRole New power role that the port is switching into.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void switchPowerRole(String portName, @HalUsbPowerRole int powerRole,
+            long transactionId);
+
+    /**
+     * Invoked to switch USB port data role.
+     *
+     * @param portName Port Identifier.
+     * @param dataRole New data role that the port is switching into.
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
+     */
+    public void switchDataRole(String portName, @HalUsbDataRole int dataRole, long transactionId);
+
+    /**
+     * Invoked to query the version of current hal implementation.
+     */
+    public @UsbHalVersion int getUsbHalVersion() throws RemoteException;
+
+    /**
+     * Invoked to enable/disable UsbData on the specified port.
+     *
+     * @param portName Port Identifier.
+     * @param enable Enable USB data when true.
+     *               Disable when false.
+     * @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.
+     * @param callback callback object to be invoked when the operation is complete.
+     * @return True when the operation is asynchronous. The caller of
+     *         {@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 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.
+     * @param limit limit power transfer when true. Port wouldn't charge or power USB accessoried
+     *              when set.
+     *              Lift power transfer restrictions when false.
+     * @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 enableLimitPowerTransfer(String portName, boolean limit, long transactionId,
+            IUsbOperationInternal callback);
+}
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java
new file mode 100644
index 0000000..41f9fae
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java
@@ -0,0 +1,45 @@
+/*
+ * 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.usb.hal.port;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.port.UsbPortHidl;
+import com.android.server.usb.hal.port.UsbPortAidl;
+import com.android.server.usb.UsbPortManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbPortHalInstance {
+
+    public static UsbPortHal getInstance(UsbPortManager portManager, IndentingPrintWriter pw) {
+
+        logAndPrint(Log.DEBUG, null, "Querying USB HAL version");
+        if (UsbPortHidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, null, "USB HAL HIDL present");
+            return new UsbPortHidl(portManager, pw);
+        }
+        if (UsbPortAidl.isServicePresent(null)) {
+            logAndPrint(Log.INFO, null, "USB HAL AIDL present");
+            return new UsbPortAidl(portManager, pw);
+        }
+
+        return null;
+    }
+}
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
new file mode 100644
index 0000000..c1d7635
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java
@@ -0,0 +1,500 @@
+/*
+ * 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.usb.hal.port;
+
+import static android.hardware.usb.UsbManager.USB_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.USB_HAL_V1_0;
+import static android.hardware.usb.UsbManager.USB_HAL_V1_1;
+import static android.hardware.usb.UsbManager.USB_HAL_V1_2;
+import static android.hardware.usb.UsbManager.USB_HAL_V1_3;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED;
+import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
+import static android.hardware.usb.UsbPortStatus.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;
+
+import android.annotation.Nullable;
+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;
+import android.hardware.usb.V1_1.PortStatus_1_1;
+import android.hardware.usb.V1_2.IUsbCallback;
+import android.hardware.usb.V1_0.PortRole;
+import android.hardware.usb.V1_2.PortStatus;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbPortManager;
+import com.android.server.usb.hal.port.RawPortInfo;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbPortHidl implements UsbPortHal {
+    private static final String TAG = UsbPortHidl.class.getSimpleName();
+    // Cookie sent for usb hal death notification.
+    private static final int USB_HAL_DEATH_COOKIE = 1000;
+    // Proxy object for the usb hal daemon.
+    @GuardedBy("mLock")
+    private IUsb mProxy;
+    private UsbPortManager mPortManager;
+    public IndentingPrintWriter mPw;
+    // Mutex for all mutable shared state.
+    private final Object mLock = new Object();
+    // Callback when the UsbPort status is changed by the kernel.
+    private HALCallback mHALCallback;
+    private boolean mSystemReady;
+    // Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus;
+    private static int sUsbDataStatus = USB_DATA_STATUS_UNKNOWN;
+
+    public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
+        int version;
+        synchronized(mLock) {
+            if (mProxy == null) {
+                throw new RemoteException("IUsb not initialized yet");
+            }
+            if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) {
+                version = USB_HAL_V1_3;
+            } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) {
+                version = USB_HAL_V1_2;
+            } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) {
+                version = USB_HAL_V1_1;
+            } else {
+                version = USB_HAL_V1_0;
+            }
+            logAndPrint(Log.INFO, null, "USB HAL HIDL version: " + version);
+            return version;
+        }
+    }
+
+    final class DeathRecipient implements IHwBinder.DeathRecipient {
+        public IndentingPrintWriter pw;
+
+        DeathRecipient(IndentingPrintWriter pw) {
+            this.pw = pw;
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == USB_HAL_DEATH_COOKIE) {
+                logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
+                synchronized (mLock) {
+                    mProxy = null;
+                }
+            }
+        }
+    }
+
+    final class ServiceNotification extends IServiceNotification.Stub {
+        @Override
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
+            connectToProxy(null);
+        }
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            if (mProxy != null) {
+                return;
+            }
+
+            try {
+                mProxy = IUsb.getService();
+                mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
+                mProxy.setCallback(mHALCallback);
+                mProxy.queryPortStatus();
+                //updateUsbHalVersion();
+            } catch (NoSuchElementException e) {
+                logAndPrintException(pw, "connectToProxy: usb hal service not found."
+                        + " Did the service fail to start?", e);
+            } catch (RemoteException e) {
+                logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
+            }
+        }
+    }
+
+    @Override
+    public void systemReady() {
+        mSystemReady = true;
+    }
+
+    static boolean isServicePresent(IndentingPrintWriter pw) {
+        try {
+            IUsb.getService(true);
+        } catch (NoSuchElementException e) {
+            logAndPrintException(pw, "connectToProxy: usb hidl hal service not found.", e);
+            return false;
+        } catch (RemoteException e) {
+            logAndPrintException(pw, "IUSB hal service present but failed to get service", e);
+        }
+
+        return true;
+    }
+
+    public UsbPortHidl(UsbPortManager portManager, IndentingPrintWriter pw) {
+        mPortManager = Objects.requireNonNull(portManager);
+        mPw = pw;
+        mHALCallback = new HALCallback(null, mPortManager, this);
+        try {
+            ServiceNotification serviceNotification = new ServiceNotification();
+
+            boolean ret = IServiceManager.getService()
+                    .registerForNotifications("android.hardware.usb@1.0::IUsb",
+                            "", serviceNotification);
+            if (!ret) {
+                logAndPrint(Log.ERROR, null,
+                        "Failed to register service start notification");
+            }
+        } catch (RemoteException e) {
+            logAndPrintException(null,
+                    "Failed to register service start notification", e);
+            return;
+        }
+        connectToProxy(mPw);
+    }
+
+    @Override
+    public void enableContaminantPresenceDetection(String portName, boolean enable,
+            long transactionId) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
+                return;
+            }
+
+            try {
+                // Oneway call into the hal. Use the castFrom method from HIDL.
+                android.hardware.usb.V1_2.IUsb proxy =
+                        android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
+                proxy.enableContaminantPresenceDetection(portName, enable);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set contaminant detection", e);
+            } catch (ClassCastException e)  {
+                logAndPrintException(mPw, "Method only applicable to V1.2 or above implementation",
+                    e);
+            }
+        }
+    }
+
+    @Override
+    public void queryPortStatus(long transactionId) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
+                return;
+            }
+
+            try {
+                mProxy.queryPortStatus();
+            } catch (RemoteException e) {
+                logAndPrintException(null, "ServiceStart: Failed to query port status", e);
+            }
+       }
+    }
+
+    @Override
+    public void switchMode(String portId, @HalUsbPortMode int newMode, long transactionId) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.type = PortRoleType.MODE;
+            newRole.role = newMode;
+            try {
+                mProxy.switchRole(portId, newRole);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB port mode: "
+                    + "portId=" + portId
+                    + ", newMode=" + UsbPort.modeToString(newRole.role), e);
+            }
+        }
+    }
+
+    @Override
+    public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole,
+            long transactionId) {
+        synchronized (mLock) {
+            if (mProxy == null) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.type = PortRoleType.POWER_ROLE;
+            newRole.role = newPowerRole;
+            try {
+                mProxy.switchRole(portId, newRole);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId
+                    + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role), e);
+            }
+        }
+    }
+
+    @Override
+    public void enableLimitPowerTransfer(String portName, boolean limit, 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 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) {
+                logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !");
+                return;
+            }
+
+            PortRole newRole = new PortRole();
+            newRole.type = PortRoleType.DATA_ROLE;
+            newRole.role = newDataRole;
+            try {
+                mProxy.switchRole(portId, newRole);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId
+                    + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role), e);
+            }
+        }
+    }
+
+    @Override
+    public boolean enableUsbData(String portName, boolean enable, long transactionId,
+            IUsbOperationInternal callback) {
+        int halVersion;
+
+        try {
+            halVersion = getUsbHalVersion();
+        } catch (RemoteException e) {
+            logAndPrintException(mPw, "Failed to query USB HAL version. opID:"
+                    + transactionId
+                    + " portId:" + portName, e);
+            return false;
+        }
+
+        if (halVersion != USB_HAL_V1_3) {
+            try {
+                callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED);
+            } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+                        + transactionId
+                        + " portId:" + portName, e);
+            }
+            return false;
+        }
+
+        boolean success;
+        synchronized(mLock) {
+            try {
+                android.hardware.usb.V1_3.IUsb proxy
+                        = android.hardware.usb.V1_3.IUsb.castFrom(mProxy);
+                success = proxy.enableUsbDataSignal(enable);
+           } catch (RemoteException e) {
+                logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId
+                        + " portId=" + portName , e);
+                try {
+                    callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
+                } catch (RemoteException r) {
+                    logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+                            + transactionId
+                            + " portId:" + portName, r);
+                }
+                return false;
+            }
+        }
+        if (success) {
+            sUsbDataStatus = enable ? USB_DATA_STATUS_UNKNOWN : USB_DATA_STATUS_DISABLED_FORCE;
+        }
+        try {
+            callback.onOperationComplete(success
+                    ? USB_OPERATION_SUCCESS
+                    : USB_OPERATION_ERROR_INTERNAL);
+        } catch (RemoteException r) {
+            logAndPrintException(mPw, "Failed to call onOperationComplete. opID:"
+                + transactionId
+                + " portId:" + portName, r);
+        }
+        return false;
+    }
+
+    private static class HALCallback extends IUsbCallback.Stub {
+        public IndentingPrintWriter mPw;
+        public UsbPortManager mPortManager;
+        public UsbPortHidl mUsbPortHidl;
+
+        HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortHidl usbPortHidl) {
+            this.mPw = pw;
+            this.mPortManager = portManager;
+            this.mUsbPortHidl = usbPortHidl;
+        }
+
+        public void notifyPortStatusChange(
+                ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
+            if (!mUsbPortHidl.mSystemReady) {
+                return;
+            }
+
+            if (retval != Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+            for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
+                RawPortInfo temp = new RawPortInfo(current.portName,
+                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+                        current.currentMode,
+                        current.canChangeMode, current.currentPowerRole,
+                        current.canChangePowerRole,
+                        current.currentDataRole, current.canChangeDataRole,
+                        false, CONTAMINANT_PROTECTION_NONE,
+                        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);
+            }
+
+            mPortManager.updatePorts(newPortInfo);
+        }
+
+
+        public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus,
+                int retval) {
+            if (!mUsbPortHidl.mSystemReady) {
+                return;
+            }
+
+            if (retval != Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+            int numStatus = currentPortStatus.size();
+            for (int i = 0; i < numStatus; i++) {
+                PortStatus_1_1 current = currentPortStatus.get(i);
+                RawPortInfo temp = new RawPortInfo(current.status.portName,
+                        current.supportedModes, CONTAMINANT_PROTECTION_NONE,
+                        current.currentMode,
+                        current.status.canChangeMode, current.status.currentPowerRole,
+                        current.status.canChangePowerRole,
+                        current.status.currentDataRole, current.status.canChangeDataRole,
+                        false, CONTAMINANT_PROTECTION_NONE,
+                        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);
+            }
+            mPortManager.updatePorts(newPortInfo);
+        }
+
+        public void notifyPortStatusChange_1_2(
+                ArrayList<PortStatus> currentPortStatus, int retval) {
+            if (!mUsbPortHidl.mSystemReady) {
+                return;
+            }
+
+            if (retval != Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
+
+            int numStatus = currentPortStatus.size();
+            for (int i = 0; i < numStatus; i++) {
+                PortStatus current = currentPortStatus.get(i);
+                RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
+                        current.status_1_1.supportedModes,
+                        current.supportedContaminantProtectionModes,
+                        current.status_1_1.currentMode,
+                        current.status_1_1.status.canChangeMode,
+                        current.status_1_1.status.currentPowerRole,
+                        current.status_1_1.status.canChangePowerRole,
+                        current.status_1_1.status.currentDataRole,
+                        current.status_1_1.status.canChangeDataRole,
+                        current.supportsEnableContaminantPresenceProtection,
+                        current.contaminantProtectionStatus,
+                        current.supportsEnableContaminantPresenceDetection,
+                        current.contaminantDetectionStatus,
+                        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);
+            }
+            mPortManager.updatePorts(newPortInfo);
+        }
+
+        public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
+            if (retval == Status.SUCCESS) {
+                UsbPortManager.logAndPrint(Log.INFO, mPw, portName + " role switch successful");
+            } else {
+                UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed");
+            }
+        }
+    }
+}
diff --git a/services/wallpapereffectsgeneration/OWNERS b/services/wallpapereffectsgeneration/OWNERS
new file mode 100644
index 0000000..d2d3e2c0
--- /dev/null
+++ b/services/wallpapereffectsgeneration/OWNERS
@@ -0,0 +1,4 @@
+susharon@google.com
+shanh@google.com
+huiwu@google.com
+srazdan@google.com
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/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index 55957bd..389df80 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -259,10 +259,10 @@
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
             BluetoothDevice activeBluetoothDevice = source.readParcelable(
-                    ClassLoader.getSystemClassLoader());
+                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
             List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
             source.readParcelableList(supportedBluetoothDevices,
-                    ClassLoader.getSystemClassLoader());
+                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
             return new CallAudioState(isMuted, route,
                     supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
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 d63cdc0..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
     //**********************************************************************************************
@@ -3546,9 +3537,9 @@
             mIsBlocked = in.readByte() != 0;
             mIsInContacts = in.readByte() != 0;
             CallScreeningService.ParcelableCallResponse response
-                    = in.readParcelable(CallScreeningService.class.getClassLoader());
+                    = in.readParcelable(CallScreeningService.class.getClassLoader(), android.telecom.CallScreeningService.ParcelableCallResponse.class);
             mCallResponse = response == null ? null : response.toCallResponse();
-            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader());
+            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
         }
 
         @NonNull
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index be5fae4..1172e13 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -272,17 +272,17 @@
     }
 
     private ConnectionRequest(Parcel in) {
-        mAccountHandle = in.readParcelable(getClass().getClassLoader());
-        mAddress = in.readParcelable(getClass().getClassLoader());
-        mExtras = in.readParcelable(getClass().getClassLoader());
+        mAccountHandle = in.readParcelable(getClass().getClassLoader(), android.telecom.PhoneAccountHandle.class);
+        mAddress = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
+        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
         mVideoState = in.readInt();
         mTelecomCallId = in.readString();
         mShouldShowIncomingCallUi = in.readInt() == 1;
-        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
-        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
+        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
 
         mParticipants = new ArrayList<Uri>();
-        in.readList(mParticipants, getClass().getClassLoader());
+        in.readList(mParticipants, getClass().getClassLoader(), android.net.Uri.class);
 
         mIsAdhocConference = in.readInt() == 1;
     }
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index ed7b79f..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;
@@ -287,7 +303,7 @@
             int tone = source.readInt();
             int telephonyDisconnectCause = source.readInt();
             int telephonyPreciseDisconnectCause = source.readInt();
-            ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+            ImsReasonInfo imsReasonInfo = source.readParcelable(null, android.telephony.ims.ImsReasonInfo.class);
             return new DisconnectCause(code, label, description, reason, tone,
                     telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
         }
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 320308c..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 =
@@ -623,9 +675,9 @@
             ClassLoader classLoader = ParcelableCall.class.getClassLoader();
             String id = source.readString();
             int state = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             List<String> cannedSmsResponses = new ArrayList<>();
-            source.readList(cannedSmsResponses, classLoader);
+            source.readList(cannedSmsResponses, classLoader, java.lang.String.class);
             int capabilities = source.readInt();
             int properties = source.readInt();
             long connectTimeMillis = source.readLong();
@@ -633,28 +685,31 @@
             int handlePresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            GatewayInfo gatewayInfo = source.readParcelable(classLoader);
-            PhoneAccountHandle accountHandle = source.readParcelable(classLoader);
+            GatewayInfo gatewayInfo = source.readParcelable(classLoader, android.telecom.GatewayInfo.class);
+            PhoneAccountHandle accountHandle = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             boolean isVideoCallProviderChanged = source.readByte() == 1;
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
-            source.readList(childCallIds, classLoader);
-            StatusHints statusHints = source.readParcelable(classLoader);
+            source.readList(childCallIds, classLoader, java.lang.String.class);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
             int videoState = source.readInt();
             List<String> conferenceableCallIds = new ArrayList<>();
-            source.readList(conferenceableCallIds, classLoader);
+            source.readList(conferenceableCallIds, classLoader, java.lang.String.class);
             Bundle intentExtras = source.readBundle(classLoader);
             Bundle extras = source.readBundle(classLoader);
             int supportedAudioRoutes = source.readInt();
             boolean isRttCallChanged = source.readByte() == 1;
-            ParcelableRttCall rttCall = source.readParcelable(classLoader);
+            ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class);
             long creationTimeMillis = source.readLong();
             int callDirection = source.readInt();
             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/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index 1f8aafb..e57c833 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -292,24 +292,24 @@
         @Override
         public ParcelableConference createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConference.class.getClassLoader();
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             int state = source.readInt();
             int capabilities = source.readInt();
             List<String> connectionIds = new ArrayList<>(2);
-            source.readList(connectionIds, classLoader);
+            source.readList(connectionIds, classLoader, java.lang.String.class);
             long connectTimeMillis = source.readLong();
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             int videoState = source.readInt();
-            StatusHints statusHints = source.readParcelable(classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
             Bundle extras = source.readBundle(classLoader);
             int properties = source.readInt();
             long connectElapsedTimeMillis = source.readLong();
-            Uri address = source.readParcelable(classLoader);
+            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             boolean isRingbackRequested = source.readInt() == 1;
             int callDirection = source.readInt();
 
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index 2b9ce9b..7b83338 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -261,10 +261,10 @@
         public ParcelableConnection createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConnection.class.getClassLoader();
 
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             int state = source.readInt();
             int capabilities = source.readInt();
-            Uri address = source.readParcelable(classLoader);
+            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
@@ -274,8 +274,8 @@
             boolean ringbackRequested = source.readByte() == 1;
             boolean audioModeIsVoip = source.readByte() == 1;
             long connectTimeMillis = source.readLong();
-            StatusHints statusHints = source.readParcelable(classLoader);
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             List<String> conferenceableConnectionIds = new ArrayList<>();
             source.readStringList(conferenceableConnectionIds);
             Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true);
diff --git a/telecomm/java/android/telecom/ParcelableRttCall.java b/telecomm/java/android/telecom/ParcelableRttCall.java
index fbcf486..b88473a 100644
--- a/telecomm/java/android/telecom/ParcelableRttCall.java
+++ b/telecomm/java/android/telecom/ParcelableRttCall.java
@@ -46,8 +46,8 @@
 
     protected ParcelableRttCall(Parcel in) {
         mRttMode = in.readInt();
-        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
-        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableRttCall> CREATOR = new Creator<ParcelableRttCall>() {
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/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index 2589d95..d9f89d5 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -84,7 +84,7 @@
     }
 
     private PhoneAccountSuggestion(Parcel in) {
-        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader());
+        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader(), android.telecom.PhoneAccountHandle.class);
         mReason = in.readInt();
         mShouldAutoSelect = in.readByte() != 0;
     }
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 762c93a..2faecc2 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -132,8 +132,8 @@
 
     private StatusHints(Parcel in) {
         mLabel = in.readCharSequence();
-        mIcon = in.readParcelable(getClass().getClassLoader());
-        mExtras = in.readParcelable(getClass().getClassLoader());
+        mIcon = in.readParcelable(getClass().getClassLoader(), android.graphics.drawable.Icon.class);
+        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
     }
 
     @Override
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/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java
index 2b355ae..6d673fb 100644
--- a/telephony/java/android/telephony/AvailableNetworkInfo.java
+++ b/telephony/java/android/telephony/AvailableNetworkInfo.java
@@ -185,9 +185,9 @@
         mMccMncs = new ArrayList<>();
         in.readStringList(mMccMncs);
         mBands = new ArrayList<>();
-        in.readList(mBands, Integer.class.getClassLoader());
+        in.readList(mBands, Integer.class.getClassLoader(), java.lang.Integer.class);
         mRadioAccessSpecifiers = new ArrayList<>();
-        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader());
+        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader(), android.telephony.RadioAccessSpecifier.class);
     }
 
     public AvailableNetworkInfo(int subId, int priority, @NonNull List<String> mccMncs,
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 0aa4b58..29152f1 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -294,8 +294,8 @@
 
     /** @hide */
     public BarringInfo(Parcel p) {
-        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
-        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
+        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
+        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader(), android.telephony.BarringInfo.BarringServiceInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 0c258f4..b7bef39 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -53,9 +53,9 @@
     }
 
     private CallAttributes(Parcel in) {
-        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader());
+        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader(), android.telephony.PreciseCallState.class);
         this.mNetworkType = in.readInt();
-        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader());
+        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader(), android.telephony.CallQuality.class);
     }
 
     // getters
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 21967f4..d5c846d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -175,7 +175,10 @@
     /**
      * This flag specifies whether VoLTE availability is based on provisioning. By default this is
      * false.
+     * Used for UCE to determine if EAB provisioning checks should be based on provisioning.
+     * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead.
      */
+    @Deprecated
     public static final String
             KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
 
@@ -879,7 +882,12 @@
     /**
      * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
      * Calling.
+
+     * Combines VoLTE, VT, VoWiFI calling provisioning into one parameter.
+     * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for
+     * finer-grained control.
      */
+    @Deprecated
     public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
             = "carrier_volte_provisioning_required_bool";
 
@@ -893,7 +901,11 @@
      * and enable the UT over IMS capability for the subscription when the subscription is loaded.
      *
      * The default value for this key is {@code false}.
+     *
+     * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for
+     * determining if UT requires provisioning.
      */
+    @Deprecated
     public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL =
             "carrier_ut_provisioning_required_bool";
 
@@ -5173,6 +5185,95 @@
         /**  E911 RTP inactivity occurred when call is connected. */
         public static final int E911_RTP_INACTIVITY_ON_CONNECTED = 4;
 
+        /**
+         * A bundle which specifies the MMTEL capability and registration technology
+         * that requires provisioning. If a tuple is not present, the
+         * framework will not require that the tuple requires provisioning before
+         * enabling the capability.
+         * <p> Possible keys in this bundle are
+         * <ul>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li>
+         * </ul>
+         * <p> The values are defined in
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech}
+         */
+        public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE =
+                KEY_PREFIX + "mmtel_requires_provisioning_bundle";
+
+        /**
+         * This MmTelFeature supports Voice calling (IR.92)
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE
+         */
+        public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_voice_int_array";
+
+        /**
+         * This MmTelFeature supports Video (IR.94)
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO
+         */
+        public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_video_int_array";
+
+        /**
+         * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92)
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT
+         */
+        public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_ut_int_array";
+
+        /**
+         * This MmTelFeature supports SMS (IR.92)
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS
+         */
+        public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_sms_int_array";
+
+        /**
+         * This MmTelFeature supports Call Composer (section 2.4 of RCC.20)
+         * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER
+         */
+        public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_call_composer_int_array";
+
+        /**
+         * A bundle which specifies the RCS capability and registration technology
+         * that requires provisioning. If a tuple is not present, the
+         * framework will not require that the tuple requires provisioning before
+         * enabling the capability.
+         * <p> Possible keys in this bundle are
+         * <ul>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li>
+         *     <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li>
+         * </ul>
+         * <p> The values are defined in
+         * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech}
+         */
+        public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE =
+                KEY_PREFIX + "rcs_requires_provisioning_bundle";
+
+        /**
+         * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
+         * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
+         * If not set, this RcsFeature should not service capability requests.
+         * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE
+         */
+        public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_options_uce_int_array";
+
+        /**
+         * This carrier supports User Capability Exchange using a presence server as defined by the
+         * framework. If set, the RcsFeature should support capability exchange using a presence
+         * server. If not set, this RcsFeature should not publish capabilities or service capability
+         * requests using presence.
+         * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE
+         */
+        public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY =
+                KEY_PREFIX + "key_capability_type_presence_uce_int_array";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -5209,6 +5310,20 @@
                     "+g.gsma.rcs.botversion=\"#=1,#=2\"",
                     "+g.gsma.rcs.cpimext"});
 
+            /**
+             * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
+             */
+            PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle();
+            defaults.putPersistableBundle(
+                    KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array);
+
+            /**
+             * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
+             */
+            PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle();
+            defaults.putPersistableBundle(
+                    KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array);
+
             defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true);
             defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true);
             defaults.putBoolean(KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false);
@@ -7573,8 +7688,8 @@
         public static final String KEY_EPDG_PCO_ID_IPV4_INT = KEY_PREFIX + "epdg_pco_id_ipv4_int";
 
         /** Controls if the IKE tunnel setup supports EAP-AKA fast reauth */
-        public static final String KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL =
-                KEY_PREFIX + "enable_support_for_eap_aka_fast_reauth_bool";
+        public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL =
+                KEY_PREFIX + "supports_eap_aka_fast_reauth_bool";
 
         /** @hide */
         @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT})
@@ -7722,7 +7837,7 @@
             defaults.putBoolean(KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL, false);
             defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0);
             defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0);
-            defaults.putBoolean(KEY_ENABLE_SUPPORT_FOR_EAP_AKA_FAST_REAUTH_BOOL, false);
+            defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false);
 
             return defaults;
         }
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 4db00cf..b4b8aee 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -379,7 +379,7 @@
         mBands = in.createIntArray();
         mBandwidth = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 13d9373..90e6295 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -297,7 +297,7 @@
         mCpid = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 9b463da..72282cd 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -313,7 +313,7 @@
         mPsc = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index cd22abd..f5ba3ab 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -326,7 +326,7 @@
         mCsiRsrq = in.readInt();
         mCsiSinr = in.readInt();
         mCsiCqiTableIndex = in.readInt();
-        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader());
+        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
         mSsRsrp = in.readInt();
         mSsRsrq = in.readInt();
         mSsSinr = in.readInt();
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 957f683..837124f 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -105,7 +105,7 @@
         isDcNrRestricted = source.readBoolean();
         isNrAvailable = source.readBoolean();
         isEnDcAvailable = source.readBoolean();
-        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
+        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 2758e12..b0ff949 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -163,6 +163,26 @@
         return new SipDelegateManager(mContext, subscriptionId, sRcsCache, sTelephonyCache);
     }
 
+
+    /**
+     * Create an instance of {@link ProvisioningManager} for the subscription id specified.
+     * <p>
+     * Provides a ProvisioningManager instance to carrier apps to update carrier provisioning
+     * information, as well as provides a callback so that apps can listen for changes
+     * in MMTEL/RCS provisioning
+     * @param subscriptionId The ID of the subscription that this ProvisioningManager will use.
+     * @throws IllegalArgumentException if the subscription is invalid.
+     * @return a ProvisioningManager instance with the specific subscription ID.
+     */
+    @NonNull
+    public ProvisioningManager getProvisioningManager(int subscriptionId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
+            throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
+        }
+
+        return new ProvisioningManager(subscriptionId);
+    }
+
     private static IImsRcsController getIImsRcsControllerInterface() {
         return IImsRcsController.Stub.asInterface(
                 TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 6a80766..c18443e 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -311,12 +311,12 @@
         mRejectCause = source.readInt();
         mEmergencyOnly = source.readBoolean();
         mAvailableServices = new ArrayList<>();
-        source.readList(mAvailableServices, Integer.class.getClassLoader());
-        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader());
+        source.readList(mAvailableServices, Integer.class.getClassLoader(), java.lang.Integer.class);
+        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
         mVoiceSpecificInfo = source.readParcelable(
-                VoiceSpecificRegistrationInfo.class.getClassLoader());
+                VoiceSpecificRegistrationInfo.class.getClassLoader(), android.telephony.VoiceSpecificRegistrationInfo.class);
         mDataSpecificInfo = source.readParcelable(
-                DataSpecificRegistrationInfo.class.getClassLoader());
+                DataSpecificRegistrationInfo.class.getClassLoader(), android.telephony.DataSpecificRegistrationInfo.class);
         mNrState = source.readInt();
         mRplmn = source.readString();
         mIsUsingCarrierAggregation = source.readBoolean();
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index a3aaf61..63e3468 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -150,7 +150,7 @@
         mMaxActiveDataSubscriptions = in.readInt();
         mNetworkValidationBeforeSwitchSupported = in.readBoolean();
         mLogicalModemList = new ArrayList<>();
-        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader());
+        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader(), android.telephony.ModemInfo.class);
         mDeviceNrCapabilities = in.createIntArray();
     }
 
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index ce2f3f9..2670b03 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -125,9 +125,9 @@
         mId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader());
+        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader(), android.net.LinkProperties.class);
         mFailCause = in.readInt();
-        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader());
+        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5affb62..70da9b9 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -479,7 +479,7 @@
         mIsEmergencyOnly = in.readInt() != 0;
         mArfcnRsrpBoost = in.readInt();
         synchronized (mNetworkRegistrationInfos) {
-            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader());
+            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader(), android.telephony.NetworkRegistrationInfo.class);
         }
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index b7bc467..f74ef0f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -275,12 +275,12 @@
     public SignalStrength(Parcel in) {
         if (DBG) log("Size of signalstrength parcel:" + in.dataSize());
 
-        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader());
-        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader());
-        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader());
-        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
-        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
-        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
+        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader(), android.telephony.CellSignalStrengthCdma.class);
+        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader(), android.telephony.CellSignalStrengthGsm.class);
+        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader(), android.telephony.CellSignalStrengthWcdma.class);
+        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader(), android.telephony.CellSignalStrengthTdscdma.class);
+        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthLte.class);
+        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthNr.class);
         mTimestampMillis = in.readLong();
     }
 
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 574a356..250e55c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3892,6 +3892,11 @@
      * {@link #PHONE_NUMBER_SOURCE_UICC UICC} and decide if the previously set phone number
      * of source {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated.
      *
+     * <p>The API provides no guarantees of what format the number is in: the format can vary
+     * depending on the {@code source} and the network etc. Programmatic parsing should be done
+     * cautiously, for example, after formatting the number to a consistent format with
+     * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
+     *
      * <p>Note the assumption is that one subscription (which usually means one SIM) has
      * only one phone number. The multiple sources backup each other so hopefully at least one
      * is availavle. For example, for a carrier that doesn't typically set phone numbers
@@ -3950,6 +3955,11 @@
      * from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER}
      * > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}.
      *
+     * <p>The API provides no guarantees of what format the number is in: the format can vary
+     * depending on the underlying source and the network etc. Programmatic parsing should be done
+     * cautiously, for example, after formatting the number to a consistent format with
+     * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
+     *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
      * @return the phone number, or an empty string if not available.
@@ -3988,6 +3998,9 @@
      * <p>The API is suitable for carrier apps to provide a phone number, for example when
      * it's not possible to update {@link #PHONE_NUMBER_SOURCE_UICC UICC} directly.
      *
+     * <p>It's recommended that the phone number is formatted to well-known formats,
+     * for example, by {@link PhoneNumberUtils} {@code formatNumber*} methods.
+     *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
      * @param number the phone number, or an empty string to remove the previously set number.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0c56de8..536517c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15138,6 +15138,15 @@
     public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4;
 
     /**
+     * Indicates the call waiting status could not be set or queried because the Fixed Dialing
+     * Numbers (FDN) feature is enabled.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5;
+
+    /**
      * @hide
      */
     @IntDef(prefix = { "CALL_WAITING_STATUS_" }, value = {
@@ -15145,6 +15154,7 @@
             CALL_WAITING_STATUS_DISABLED,
             CALL_WAITING_STATUS_UNKNOWN_ERROR,
             CALL_WAITING_STATUS_NOT_SUPPORTED,
+            CALL_WAITING_STATUS_FDN_CHECK_FAILURE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CallWaitingStatus {
@@ -15165,6 +15175,7 @@
      *                          <li>{@link #CALL_WAITING_STATUS_DISABLED}}</li>
      *                          <li>{@link #CALL_WAITING_STATUS_UNKNOWN_ERROR}}</li>
      *                          <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li>
+     *                          <li>{@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE}}</li>
      *                       </ul>
      * @hide
      */
@@ -15214,7 +15225,8 @@
      *                       {@link #CALL_WAITING_STATUS_ENABLED} or
      *                       {@link #CALL_WAITING_STATUS_DISABLED} if the operation succeeded and
      *                       {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or
-     *                       {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} if it failed.
+     *                       {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} or
+     *                       {@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE} if it failed.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ThermalMitigationRequest.java b/telephony/java/android/telephony/ThermalMitigationRequest.java
index 91ad9c3..a0676ea 100644
--- a/telephony/java/android/telephony/ThermalMitigationRequest.java
+++ b/telephony/java/android/telephony/ThermalMitigationRequest.java
@@ -100,7 +100,7 @@
 
     private ThermalMitigationRequest(Parcel in) {
         mThermalMitigationAction = in.readInt();
-        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader());
+        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader(), android.telephony.DataThrottlingRequest.class);
     }
 
      /**
diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java
index 085f882..bec715e 100644
--- a/telephony/java/android/telephony/VisualVoicemailSms.java
+++ b/telephony/java/android/telephony/VisualVoicemailSms.java
@@ -121,7 +121,7 @@
                 @Override
                 public VisualVoicemailSms createFromParcel(Parcel in) {
                     return new Builder()
-                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null))
+                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null, android.telecom.PhoneAccountHandle.class))
                             .setPrefix(in.readString())
                             .setFields(in.readBundle())
                             .setMessageBody(in.readString())
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index cb112cf..acbd64b 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1628,7 +1628,7 @@
                 .setApnName(in.readString())
                 .setProxyAddress(in.readString())
                 .setProxyPort(in.readInt())
-                .setMmsc(in.readParcelable(Uri.class.getClassLoader()))
+                .setMmsc(in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class))
                 .setMmsProxyAddress(in.readString())
                 .setMmsProxyPort(in.readInt())
                 .setUser(in.readString())
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index ef02589..ae0d4e7 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -241,24 +241,24 @@
         mProtocolType = source.readInt();
         mInterfaceName = source.readString();
         mAddresses = new ArrayList<>();
-        source.readList(mAddresses, LinkAddress.class.getClassLoader());
+        source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
         mDnsAddresses = new ArrayList<>();
-        source.readList(mDnsAddresses, InetAddress.class.getClassLoader());
+        source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mGatewayAddresses = new ArrayList<>();
-        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader());
+        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mPcscfAddresses = new ArrayList<>();
-        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader());
+        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mMtu = source.readInt();
         mMtuV4 = source.readInt();
         mMtuV6 = source.readInt();
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
-        mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
+        mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
         mQosBearerSessions = new ArrayList<>();
-        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader());
-        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader());
+        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class);
+        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class);
         mTrafficDescriptors = new ArrayList<>();
-        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader());
+        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index ec04c1a..a166a5d 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -107,8 +107,8 @@
 
     private DataProfile(Parcel source) {
         mType = source.readInt();
-        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
-        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
+        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
+        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
         mPreferred = source.readBoolean();
         mSetupTimestamp = source.readLong();
     }
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index 8c437c8..9c2a3bb 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -136,8 +136,8 @@
 
     protected Qos(@NonNull Parcel source) {
         type = source.readInt();
-        downlink = source.readParcelable(QosBandwidth.class.getClassLoader());
-        uplink = source.readParcelable(QosBandwidth.class.getClassLoader());
+        downlink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
+        uplink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index d6f0cb0..0ab7b61 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -256,11 +256,11 @@
 
     private QosBearerFilter(Parcel source) {
         localAddresses = new ArrayList<>();
-        source.readList(localAddresses, LinkAddress.class.getClassLoader());
+        source.readList(localAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
         remoteAddresses = new ArrayList<>();
-        source.readList(remoteAddresses, LinkAddress.class.getClassLoader());
-        localPort = source.readParcelable(PortRange.class.getClassLoader());
-        remotePort = source.readParcelable(PortRange.class.getClassLoader());
+        source.readList(remoteAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
+        localPort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
+        remotePort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
         protocol = source.readInt();
         typeOfServiceMask = source.readInt();
         flowLabel = source.readLong();
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index ffeb08a..dd08085 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -46,9 +46,9 @@
 
     private QosBearerSession(Parcel source) {
         qosBearerSessionId = source.readInt();
-        qos = source.readParcelable(Qos.class.getClassLoader());
+        qos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
         qosBearerFilterList = new ArrayList<>();
-        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader());
+        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader(), android.telephony.data.QosBearerFilter.class);
     }
 
     public int getQosBearerSessionId() {
diff --git a/telephony/java/android/telephony/gba/GbaAuthRequest.java b/telephony/java/android/telephony/gba/GbaAuthRequest.java
index 5366e9a..2c6021a 100644
--- a/telephony/java/android/telephony/gba/GbaAuthRequest.java
+++ b/telephony/java/android/telephony/gba/GbaAuthRequest.java
@@ -120,7 +120,7 @@
                     int token = in.readInt();
                     int subId = in.readInt();
                     int appType = in.readInt();
-                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader());
+                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader(), android.net.Uri.class);
                     int len = in.readInt();
                     byte[] protocol = new byte[len];
                     in.readByteArray(protocol);
diff --git a/telephony/java/android/telephony/ims/DelegateRequest.java b/telephony/java/android/telephony/ims/DelegateRequest.java
index c322d92..c5c9200 100644
--- a/telephony/java/android/telephony/ims/DelegateRequest.java
+++ b/telephony/java/android/telephony/ims/DelegateRequest.java
@@ -63,7 +63,7 @@
      */
     private DelegateRequest(Parcel in) {
         mFeatureTags = new ArrayList<>();
-        in.readList(mFeatureTags, null /*classLoader*/);
+        in.readList(mFeatureTags, null /*classLoader*/, java.lang.String.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 8a665dc..e6d7df3 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -843,7 +843,7 @@
         mServiceType = in.readInt();
         mCallType = in.readInt();
         mCallExtras = in.readBundle();
-        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
+        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader(), android.telephony.ims.ImsStreamMediaProfile.class);
         mEmergencyServiceCategories = in.readInt();
         mEmergencyUrns = in.createStringArrayList();
         mEmergencyCallRouting = in.readInt();
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index 6569de6..d65286f 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -1212,50 +1212,56 @@
          */
         @Override
         public void callSessionInitiating(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionInitiating(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionProgressing(ImsStreamMediaProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionProgressing(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionInitiated(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionStarted(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionTerminated(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1263,51 +1269,56 @@
          */
         @Override
         public void callSessionHeld(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionHeld(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionHoldFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionHoldReceived(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionResumed(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed(
-                        ImsCallSession.this, profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionResumed(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionResumeFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionResumeReceived(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionResumeReceived(ImsCallSession.this, profile),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1330,8 +1341,8 @@
          */
         @Override
         public void callSessionMergeComplete(IImsCallSession newSession) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> {
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
                     if (newSession != null) {
                         // New session created after conference
                         mListener.callSessionMergeComplete(new ImsCallSession(newSession));
@@ -1339,8 +1350,8 @@
                         // Session already exists. Hence no need to pass
                         mListener.callSessionMergeComplete(null);
                     }
-                }, mListenerExecutor);
-            }
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1350,11 +1361,11 @@
          */
         @Override
         public void callSessionMergeFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1362,29 +1373,29 @@
          */
         @Override
         public void callSessionUpdated(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionUpdated(ImsCallSession.this, profile),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionUpdated(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionUpdateReceived(ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionUpdateReceived(ImsCallSession.this, profile),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1393,30 +1404,33 @@
         @Override
         public void callSessionConferenceExtended(IImsCallSession newSession,
                 ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionConferenceExtended(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionConferenceExtended(ImsCallSession.this,
+                            new ImsCallSession(newSession), profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionConferenceExtendFailed(
-                        ImsCallSession.this, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionConferenceExtendFailed(
+                            ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionConferenceExtendReceived(IImsCallSession newSession,
                 ImsCallProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                            new ImsCallSession(newSession), profile);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1425,38 +1439,41 @@
          */
         @Override
         public void callSessionInviteParticipantsRequestDelivered() {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionInviteParticipantsRequestDelivered(
-                        ImsCallSession.this), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionInviteParticipantsRequestDelivered(
+                            ImsCallSession.this);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                            reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestDelivered() {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRemoveParticipantsRequestDelivered(
-                        ImsCallSession.this), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                            reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1464,11 +1481,11 @@
          */
         @Override
         public void callSessionConferenceStateUpdated(ImsConferenceState state) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1476,11 +1493,12 @@
          */
         @Override
         public void callSessionUssdMessageReceived(int mode, String ussdMessage) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
-                        ussdMessage), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
+                            ussdMessage);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1496,11 +1514,12 @@
          */
         @Override
         public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+                            targetNetworkType);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1509,11 +1528,12 @@
         @Override
         public void callSessionHandover(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+                            targetNetworkType, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1522,11 +1542,12 @@
         @Override
         public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+                            targetNetworkType, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1534,11 +1555,11 @@
          */
         @Override
         public void callSessionTtyModeReceived(int mode) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionTtyModeReceived(ImsCallSession.this, mode),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1548,20 +1569,22 @@
          *      otherwise.
          */
         public void callSessionMultipartyStateChanged(boolean isMultiParty) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
-                        isMultiParty), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
+                            isMultiParty);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionSuppServiceReceived(ImsCallSession.this,
-                        suppServiceInfo), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionSuppServiceReceived(ImsCallSession.this,
+                            suppServiceInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1569,11 +1592,12 @@
          */
         @Override
         public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
-                        callProfile), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
+                            callProfile);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1581,11 +1605,11 @@
          */
         @Override
         public void callSessionRttModifyResponseReceived(int status) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRttModifyResponseReceived(status),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRttModifyResponseReceived(status);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1593,10 +1617,11 @@
          */
         @Override
         public void callSessionRttMessageReceived(String rttMessage) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRttMessageReceived(rttMessage);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1604,28 +1629,29 @@
          */
         @Override
         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRttAudioIndicatorChanged(profile),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRttAudioIndicatorChanged(profile);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionTransferred() {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionTransferred(ImsCallSession.this);
+                }
+            }, mListenerExecutor);
         }
 
         @Override
         public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo),
-                        mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1634,10 +1660,11 @@
          */
         @Override
         public void callSessionDtmfReceived(char dtmf) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived(
-                        dtmf), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionDtmfReceived(dtmf);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1645,10 +1672,11 @@
          */
         @Override
         public void callQualityChanged(CallQuality callQuality) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged(
-                        callQuality), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callQualityChanged(callQuality);
+                }
+            }, mListenerExecutor);
         }
 
         /**
@@ -1658,11 +1686,12 @@
         @Override
         public void callSessionRtpHeaderExtensionsReceived(
                 @NonNull List<RtpHeaderExtension> extensions) {
-            if (mListener != null) {
-                TelephonyUtils.runWithCleanCallingIdentity(()->
-                        mListener.callSessionRtpHeaderExtensionsReceived(
-                        new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mListener != null) {
+                    mListener.callSessionRtpHeaderExtensionsReceived(
+                            new ArraySet<RtpHeaderExtension>(extensions));
+                }
+            }, mListenerExecutor);
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 1fa5f52..d4d8c44 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -133,7 +133,7 @@
 
         for (int i = 0; i < size; ++i) {
             String user = in.readString();
-            Bundle state = in.readParcelable(null);
+            Bundle state = in.readParcelable(null, android.os.Bundle.class);
             mParticipants.put(user, state);
         }
     }
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index c663e39..d451107 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -141,8 +141,8 @@
     public ImsExternalCallState(Parcel in) {
         mCallId = in.readInt();
         ClassLoader classLoader = ImsExternalCallState.class.getClassLoader();
-        mAddress = in.readParcelable(classLoader);
-        mLocalAddress = in.readParcelable(classLoader);
+        mAddress = in.readParcelable(classLoader, android.net.Uri.class);
+        mLocalAddress = in.readParcelable(classLoader, android.net.Uri.class);
         mIsPullable = (in.readInt() != 0);
         mCallState = in.readInt();
         mCallType = in.readInt();
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
index ccb3231..b77d306 100644
--- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -153,7 +153,7 @@
         mTransportType = source.readInt();
         mImsAttributeFlags = source.readInt();
         mFeatureTags = new ArrayList<>();
-        source.readList(mFeatureTags, null /*classloader*/);
+        source.readList(mFeatureTags, null /*classloader*/, java.lang.String.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 868dea6a..9f4b77e 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -365,8 +365,8 @@
         serviceClass = in.readInt();
         result = in.readInt();
         mSsInfo = in.createIntArray();
-        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
-        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
+        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsCallForwardInfo.class);
+        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsSsInfo.class);
     }
 
     public static final @android.annotation.NonNull Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index dbf4c99..677c1a9 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -34,6 +34,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.aidl.IFeatureProvisioningCallback;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.feature.MmTelFeature;
@@ -54,18 +55,12 @@
  * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
  * applications and may vary. It is up to the carrier and OEM applications to ensure that the
  * correct provisioning keys are being used when integrating with a vendor's ImsService.
- *
- * Note: For compatibility purposes, the integer values [0 - 99] used in
- * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
- * previously defined in the Android framework. Please do not redefine new provisioning keys in this
- * range or it may generate collisions with existing keys. Some common constants have also been
- * defined in this class to make integrating with other system apps easier.
- * @hide
  */
-@SystemApi
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
 public class ProvisioningManager {
 
+    private static final String TAG = "ProvisioningManager";
+
     /**@hide*/
     @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = {
             STRING_QUERY_RESULT_ERROR_GENERIC,
@@ -76,14 +71,18 @@
 
     /**
      * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error.
+     * @hide
      */
+    @SystemApi
     public static final String STRING_QUERY_RESULT_ERROR_GENERIC =
             "STRING_QUERY_RESULT_ERROR_GENERIC";
 
     /**
      * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the
      * ImsService implementation was not ready for provisioning queries.
+     * @hide
      */
+    @SystemApi
     public static final String STRING_QUERY_RESULT_ERROR_NOT_READY =
             "STRING_QUERY_RESULT_ERROR_NOT_READY";
 
@@ -95,12 +94,16 @@
 
     /**
      * The integer result of provisioning for the queried key is disabled.
+     * @hide
      */
+    @SystemApi
     public static final int PROVISIONING_VALUE_DISABLED = 0;
 
     /**
      * The integer result of provisioning for the queried key is enabled.
+     * @hide
      */
+    @SystemApi
     public static final int PROVISIONING_VALUE_ENABLED = 1;
 
 
@@ -445,27 +448,31 @@
 
     /**
      * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
-     * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
-     * the subscription for WiFi Calling.
+     * {@link android.telephony.SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI},
+     * for the purposes of provisioning the subscription for WiFi Calling.
      *
-     * @see #getProvisioningIntValue(int)
      * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     * @hide
      */
+    @SystemApi
     public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26;
 
     /**
      * Override the user-defined WiFi mode for this subscription, defined in
-     * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning
-     * this subscription for WiFi Calling.
+     * {@link android.telephony.SubscriptionManager#WFC_MODE_CONTENT_URI},
+     * for the purposes of provisioning this subscription for WiFi Calling.
      *
      * Valid values for this key are:
      * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY},
      * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or
      * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}.
      *
-     * @see #getProvisioningIntValue(int)
      * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     * @hide
      */
+    @SystemApi
     public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
 
     /**
@@ -864,7 +871,9 @@
      * <p>Value is in String format.
      * @see #setProvisioningStringValue(int, String)
      * @see #getProvisioningStringValue(int)
+     * @hide
      */
+    @SystemApi
     public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67;
 
     /**
@@ -886,7 +895,9 @@
 
     /**
      * Callback for IMS provisioning changes.
+     * @hide
      */
+    @SystemApi
     public static class Callback {
 
         private static class CallbackBinder extends IImsConfigCallback.Stub {
@@ -956,11 +967,105 @@
         }
     }
 
+    /**
+     * Callback for IMS provisioning feature changes.
+     */
+    public static class FeatureProvisioningCallback {
+
+        private static class CallbackBinder extends IFeatureProvisioningCallback.Stub {
+
+            private final FeatureProvisioningCallback mFeatureProvisioningCallback;
+            private Executor mExecutor;
+
+            private CallbackBinder(FeatureProvisioningCallback featureProvisioningCallback) {
+                mFeatureProvisioningCallback = featureProvisioningCallback;
+            }
+
+            @Override
+            public final void onFeatureProvisioningChanged(
+                    int capability, int tech, boolean isProvisioned) {
+                final long callingIdentity = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() ->
+                            mFeatureProvisioningCallback.onFeatureProvisioningChanged(
+                                    capability, tech, isProvisioned));
+                } finally {
+                    restoreCallingIdentity(callingIdentity);
+                }
+            }
+
+            @Override
+            public final void onRcsFeatureProvisioningChanged(
+                    int capability, int tech, boolean isProvisioned) {
+                final long callingIdentity = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() ->
+                            mFeatureProvisioningCallback.onRcsFeatureProvisioningChanged(
+                                    capability, tech, isProvisioned));
+                } finally {
+                    restoreCallingIdentity(callingIdentity);
+                }
+            }
+
+            private void setExecutor(Executor executor) {
+                mExecutor = executor;
+            }
+        }
+
+        private final CallbackBinder mBinder = new CallbackBinder(this);
+
+        /**
+         * The IMS MMTEL provisioning has changed for a specific capability and IMS
+         * registration technology.
+         * @param capability The MMTEL capability that provisioning has changed for.
+         * @param tech The IMS registration technology associated with the MMTEL capability that
+         * provisioning has changed for.
+         * @param isProvisioned {@code true} if the capability is provisioned for the technology
+         * specified, or {@code false} if the capability is not provisioned for the technology
+         * specified.
+         */
+        public void onFeatureProvisioningChanged(
+                @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+                @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                boolean isProvisioned) {
+            // Base Implementation
+        }
+
+        /**
+         * The IMS RCS provisioning has changed for a specific capability and IMS
+         * registration technology.
+         * @param capability The RCS capability that provisioning has changed for.
+         * @param tech The IMS registration technology associated with the RCS capability that
+         * provisioning has changed for.
+         * @param isProvisioned {@code true} if the capability is provisioned for the technology
+         * specified, or {@code false} if the capability is not provisioned for the technology
+         * specified.
+         */
+        public void onRcsFeatureProvisioningChanged(
+                @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+                @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                boolean isProvisioned) {
+            // Base Implementation
+        }
+
+        /**@hide*/
+        public final IFeatureProvisioningCallback getBinder() {
+            return mBinder;
+        }
+
+        /**@hide*/
+        public void setExecutor(Executor executor) {
+            mBinder.setExecutor(executor);
+        }
+    }
+
     private int mSubId;
 
     /**
      * The callback for RCS provisioning changes.
+     * @hide
      */
+    @SystemApi
     public static class RcsProvisioningCallback {
         private static class CallbackBinder extends IRcsConfigCallback.Stub {
 
@@ -1098,7 +1203,9 @@
      * @param subId The ID of the subscription that this ProvisioningManager will use.
      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
      * @throws IllegalArgumentException if the subscription is invalid.
+     * @hide
      */
+    @SystemApi
     public static @NonNull ProvisioningManager createForSubscriptionId(int subId) {
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid subscription ID");
@@ -1107,7 +1214,9 @@
         return new ProvisioningManager(subId);
     }
 
-    private ProvisioningManager(int subId) {
+    /**@hide*/
+    //@SystemApi
+    public ProvisioningManager(int subId) {
         mSubId = subId;
     }
 
@@ -1116,6 +1225,12 @@
      *
      * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
      * etc...), this callback will automatically be removed.
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li>
+     * </ul>
+     *
      * @param executor The {@link Executor} to call the callback methods on
      * @param callback The provisioning callbackto be registered.
      * @see #unregisterProvisioningChangedCallback(Callback)
@@ -1126,7 +1241,9 @@
      * the {@link ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) throws ImsException {
@@ -1144,12 +1261,20 @@
      * Unregister an existing {@link Callback}. When the subscription associated with this
      * callback is removed (SIM removed, ESIM swap, etc...), this callback will automatically be
      * removed. If this method is called for an inactive subscription, it will result in a no-op.
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li>
+     * </ul>
+     *
      * @param callback The existing {@link Callback} to be removed.
      * @see #registerProvisioningChangedCallback(Executor, Callback)
      *
      * @throws IllegalArgumentException if the subscription associated with this callback is
      * invalid.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterProvisioningChangedCallback(@NonNull Callback callback) {
         try {
@@ -1160,6 +1285,62 @@
     }
 
     /**
+     * Register a new {@link FeatureProvisioningCallback}, which is used to listen for
+     * IMS feature provisioning updates.
+     * <p>
+     * When the subscription associated with this callback is removed (SIM removed,
+     * ESIM swap,etc...), this callback will automatically be removed.
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li>
+     *     <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li>
+     *     <li>or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @param executor The executor that the callback methods will be called on.
+     * @param callback The callback instance being registered.
+     * @throws ImsException if the subscription associated with this callback is
+     * valid, but the {@link ImsService the service crashed, for example. See
+     * {@link ImsException#getCode()} for a more detailed reason.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public void registerFeatureProvisioningChangedCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull FeatureProvisioningCallback callback) throws ImsException {
+        callback.setExecutor(executor);
+        try {
+            getITelephony().registerFeatureProvisioningChangedCallback(mSubId,
+                    callback.getBinder());
+        } catch (ServiceSpecificException e) {
+            throw new ImsException(e.getMessage(), e.errorCode);
+        } catch (RemoteException | IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Unregisters a previously registered {@link FeatureProvisioningCallback}
+     * instance.  When the subscription associated with this
+     * callback is removed (SIM removed, ESIM swap, etc...), this callback will
+     * automatically be removed. If this method is called for an inactive
+     * subscription, it will result in a no-op.
+     *
+     * @param callback The existing {@link FeatureProvisioningCallback} to be removed.
+     * @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback)
+     */
+    public void unregisterFeatureProvisioningChangedCallback(
+            @NonNull FeatureProvisioningCallback callback) {
+        try {
+            getITelephony().unregisterFeatureProvisioningChangedCallback(mSubId,
+                    callback.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Query for the integer value associated with the provided key.
      *
      * This operation is blocking and should not be performed on the UI thread.
@@ -1168,7 +1349,9 @@
      * @return an integer value for the provided key, or
      * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
+     * @hide
      */
+    @SystemApi
     @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public int getProvisioningIntValue(int key) {
@@ -1188,7 +1371,9 @@
      * @return a String value for the provided key, {@code null} if the key doesn't exist, or
      * {@link StringResultError} if there was an error getting the value for the provided key.
      * @throws IllegalArgumentException if the key provided was invalid.
+     * @hide
      */
+    @SystemApi
     @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @Nullable @StringResultError String getProvisioningStringValue(int key) {
@@ -1209,7 +1394,15 @@
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @param value a integer value for the provided key.
      * @return the result of setting the configuration value.
+     * @hide
+     *
+     * Note: For compatibility purposes, the integer values [0 - 99] used in
+     * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
+     * previously defined in the Android framework. Please do not redefine new provisioning keys
+     * in this range or it may generate collisions with existing keys. Some common constants have
+     * also been defined in this class to make integrating with other system apps easier.
      */
+    @SystemApi
     @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
@@ -1229,7 +1422,9 @@
      *     should be appropriately namespaced to avoid collision.
      * @param value a String value for the provided key.
      * @return the result of setting the configuration value.
+     * @hide
      */
+    @SystemApi
     @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
@@ -1249,8 +1444,14 @@
      * does not support the capability/technology combination specified, this operation will be a
      * no-op.
      *
-     * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
-     * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+     * <p>Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li>
+     *     <li>or that the calling app has carrier privileges (see</li>
+     *     <li>{@link TelephonyManager#hasCarrierPrivileges}).</li>
+     * </ul>
+     *
+     * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
      * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
      */
     @WorkerThread
@@ -1258,9 +1459,10 @@
     public void setProvisioningStatusForCapability(
             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int tech,  boolean isProvisioned) {
+
         try {
             getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech,
-                    isProvisioned);
+                        isProvisioned);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -1274,14 +1476,21 @@
      * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
      * always return {@code true}.
      *
-     * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
-     * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li>
+     *     <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li>
+     *     <li>or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
      * @return true if the device is provisioned for the capability or does not require
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
      */
     @WorkerThread
-    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public boolean getProvisioningStatusForCapability(
             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
@@ -1299,17 +1508,93 @@
      * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return
      * {@code true}.
      *
-     * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+     * @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
      * @return true if the device is provisioned for the capability or does not require
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
+     * @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead,
+     * as this only retrieves provisioning information for
+     * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+     * @hide
      */
+    @Deprecated
+    @SystemApi
     @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean getRcsProvisioningStatusForCapability(
             @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
         try {
-            return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability);
+            return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability,
+            ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Get the provisioning status for the IMS RCS capability specified.
+     *
+     * If provisioning is not required for the queried
+     * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method
+     * will always return {@code true}.
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li>
+     *     <li>or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
+     * @return true if the device is provisioned for the capability or does not require
+     * provisioning, false if the capability does require provisioning and has not been
+     * provisioned yet.
+     */
+    @WorkerThread
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public boolean getRcsProvisioningStatusForCapability(
+            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+        try {
+            return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability, tech);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Set the provisioning status for the IMS RCS capability using the specified subscription.
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE}</li>
+     *     <li>or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+
+     * Provisioning may or may not be required, depending on the carrier configuration. If
+     * provisioning is not required for the carrier associated with this subscription or the device
+     * does not support the capability/technology combination specified, this operation will be a
+     * no-op.
+     *
+     * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+     * @param isProvisioned true if the device is provisioned for the RCS capability specified,
+     *                      false otherwise.
+     * @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead,
+     * as this method only sets provisioning information for
+     * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    @WorkerThread
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void setRcsProvisioningStatusForCapability(
+            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            boolean isProvisioned) {
+        try {
+            getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability,
+                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE, isProvisioned);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -1323,7 +1608,14 @@
      * does not support the capability/technology combination specified, this operation will be a
      * no-op.
      *
-     * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+     * <p> Requires Permission:
+     * <ul>
+     *     <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li>
+     *     <li>or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
      * @param isProvisioned true if the device is provisioned for the RCS capability specified,
      *                      false otherwise.
      */
@@ -1331,31 +1623,92 @@
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setRcsProvisioningStatusForCapability(
             @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
-            boolean isProvisioned) {
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) {
         try {
             getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability,
-                    isProvisioned);
+                    tech, isProvisioned);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
     /**
+     * Indicates whether provisioning for the MMTEL capability and IMS registration technology
+     * specified is required or not
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li>
+     *     <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li>
+     *     <li> or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @return true if provisioning is required for the MMTEL capability and IMS
+     * registration technology specified, false if it is not required.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public boolean isProvisioningRequiredForCapability(
+            @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+        try {
+            return getITelephony().isProvisioningRequiredForCapability(mSubId, capability, tech);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+
+    /**
+     * Indicates whether provisioning for the RCS capability and IMS registration technology
+     * specified is required or not
+     *
+     * <p> Requires Permission:
+     * <ul>
+     *     <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li>
+     *     <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li>
+     *     <li> or that the caller has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
+     * @return true if provisioning is required for the RCS capability and IMS
+     * registration technology specified, false if it is not required.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public boolean isRcsProvisioningRequiredForCapability(
+            @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
+        try {
+            return getITelephony().isRcsProvisioningRequiredForCapability(mSubId, capability, tech);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+
+    /**
      * Notify the framework that an RCS autoconfiguration XML file has been received for
      * provisioning.
-     * <p>
-     * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
-     * carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+     *
+     * <p>Requires Permission:
+     * <ul>
+     *     <li>{@link Manifest.permission#MODIFY_PHONE_STATE},</li>
+     *     <li>or that the calling app has carrier privileges (see
+     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
+     * </ul>
+     *
      * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
-     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
         if (config == null) {
             throw new IllegalArgumentException("Must include a non-null config XML file.");
         }
+
         try {
             getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
         } catch (RemoteException e) {
@@ -1374,7 +1727,9 @@
      * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which
      * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration
      * status.
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE =
@@ -1382,7 +1737,9 @@
 
     /**
      * Integer extra to specify subscription index.
+     * @hide
      */
+    @SystemApi
     public static final String EXTRA_SUBSCRIPTION_ID =
             "android.telephony.ims.extra.SUBSCRIPTION_ID";
 
@@ -1392,22 +1749,30 @@
      * <p>The value can be {@link #STATUS_CAPABLE}, {@link #STATUS_DEVICE_NOT_CAPABLE},
      * {@link #STATUS_CARRIER_NOT_CAPABLE}, or bitwise OR of
      * {@link #STATUS_DEVICE_NOT_CAPABLE} and {@link #STATUS_CARRIER_NOT_CAPABLE}.
+     * @hide
      */
+    @SystemApi
     public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS";
 
     /**
      * RCS VoLTE single registration is supported by the device and carrier.
+     * @hide
      */
+    @SystemApi
     public static final int STATUS_CAPABLE                       = 0;
 
     /**
      * RCS VoLTE single registration is not supported by the device.
+     * @hide
      */
+    @SystemApi
     public static final int STATUS_DEVICE_NOT_CAPABLE            = 0x01;
 
     /**
      * RCS VoLTE single registration is not supported by the carrier
+     * @hide
      */
+    @SystemApi
     public static final int STATUS_CARRIER_NOT_CAPABLE           = 0x01 << 1;
 
     /**
@@ -1417,11 +1782,14 @@
      * provisioning is done using autoconfiguration, then these parameters shall be
      * sent in the HTTP get request to fetch the RCS provisioning. RCS client
      * configuration must be provided by the application before registering for the
-     * provisioning status events {@link #registerRcsProvisioningCallback()}
+     * provisioning status events
+     * {@link #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)}
      * When the IMS/RCS service receives the RCS client configuration, it will detect
      * the change in the configuration, and trigger the auto-configuration as needed.
      * @param rcc RCS client configuration {@link RcsClientConfiguration}
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
     public void setRcsClientConfiguration(
             @NonNull RcsClientConfiguration rcc) throws ImsException {
@@ -1442,18 +1810,21 @@
      * <ul>
      *     <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li>
      *     <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li>
-     *     <li>or that the caller has carrier privileges (see
+     *     <li>or that the calling app has carrier privileges (see
      *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
      * </ul>
+     *
      * @return true if IMS single registration is capable at this time, or false otherwise
-     * @throws ImsException If the remote ImsService is not available for
-     * any reason or the subscription associated with this instance is no
-     * longer active. See {@link ImsException#getCode()} for more
-     * information.
+     * @throws ImsException If the remote ImsService is not available for any reason or
+     * the subscription associated with this instance is no longer active.
+     * See {@link ImsException#getCode()} for more information.
      * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this
      * device supports IMS single registration.
+     * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
     public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
         try {
@@ -1480,8 +1851,6 @@
     * <ul>
     *     <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li>
     *     <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li>
-    *     <li>or that the caller has carrier privileges (see
-    *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
     * </ul>
     *
     * @param executor The {@link Executor} to call the callback methods on
@@ -1499,8 +1868,11 @@
     * params (See {@link #setRcsClientConfiguration}) and re register the
     * callback.
     * See {@link ImsException#getCode()} for a more detailed reason.
+    * @hide
     */
-    @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
     public void registerRcsProvisioningCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -1527,8 +1899,6 @@
      * <ul>
      *     <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li>
      *     <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li>
-     *     <li>or that the caller has carrier privileges (see
-     *         {@link TelephonyManager#hasCarrierPrivileges()}).</li>
      * </ul>
      *
      * @param callback The existing {@link RcsProvisioningCallback} to be
@@ -1536,8 +1906,11 @@
      * @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)
      * @throws IllegalArgumentException if the subscription associated with
      * this callback is invalid.
+     * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
     public void unregisterRcsProvisioningCallback(
             @NonNull RcsProvisioningCallback callback) {
@@ -1558,9 +1931,10 @@
      * {@link RcsProvisioningCallback} may expect to receive
      * {@link RcsProvisioningCallback#onConfigurationReset}, then
      * {@link RcsProvisioningCallback#onConfigurationChanged} when the new
-     * RCS configuration is received and notified by
-     * {@link #notifyRcsAutoConfigurationReceived}
+     * RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived}
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
     public void triggerRcsReconfiguration() {
         try {
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index 9c28c36..6a6c306 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -439,13 +439,13 @@
     }
 
     private RcsContactPresenceTuple(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mTimestamp = convertStringFormatTimeToInstant(in.readString());
         mStatus = in.readString();
         mServiceId = in.readString();
         mServiceVersion = in.readString();
         mServiceDescription = in.readString();
-        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
+        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
index ee02564..ea022de 100644
--- a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
+++ b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
@@ -37,7 +37,7 @@
     }
 
     private RcsContactTerminatedReason(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mReason = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 9112118..0f1b369 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -244,14 +244,14 @@
     }
 
     private RcsContactUceCapability(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mCapabilityMechanism = in.readInt();
         mSourceType = in.readInt();
         mRequestResult = in.readInt();
         List<String> featureTagList = new ArrayList<>();
         in.readStringList(featureTagList);
         mFeatureTags.addAll(featureTagList);
-        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
+        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.class);
     }
 
     @Override
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/RtpHeaderExtensionType.java b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
index af4e2347..b9ffd24 100644
--- a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
+++ b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
@@ -63,7 +63,7 @@
 
     private RtpHeaderExtensionType(Parcel in) {
         mLocalIdentifier = in.readInt();
-        mUri = in.readParcelable(Uri.class.getClassLoader());
+        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final @NonNull Creator<RtpHeaderExtensionType> CREATOR =
diff --git a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
index 1bf5cad..db0ae03 100644
--- a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
@@ -573,7 +573,7 @@
         mPrivateUserIdentifier = source.readString();
         mHomeDomain = source.readString();
         mImei = source.readString();
-        mGruu = source.readParcelable(null);
+        mGruu = source.readParcelable(null, android.net.Uri.class);
         mSipAuthHeader = source.readString();
         mSipAuthNonce = source.readString();
         mServiceRouteHeader = source.readString();
diff --git a/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl
new file mode 100644
index 0000000..63ec4aa
--- /dev/null
+++ b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.telephony.ims.aidl;
+
+/**
+ * Provides callback interface for FeatureProvisioning when a value has changed.
+ *
+ * {@hide}
+ */
+oneway interface IFeatureProvisioningCallback {
+    void onFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned);
+    void onRcsFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned);
+}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 7fdf21b..ad2e9e1 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -394,6 +394,13 @@
         public @interface MmTelCapability {}
 
         /**
+         * Undefined capability type for initialization
+         * This is used to check the upper range of MmTel capability
+         * {@hide}
+         */
+        public static final int CAPABILITY_TYPE_NONE = 0;
+
+        /**
          * This MmTelFeature supports Voice calling (IR.92)
          */
         public static final int CAPABILITY_TYPE_VOICE = 1 << 0;
@@ -419,6 +426,12 @@
         public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;
 
         /**
+         * This is used to check the upper range of MmTel capability
+         * {@hide}
+         */
+        public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER + 1;
+
+        /**
          * @hide
          */
         @Override
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 11cf0e3..70e4ef1 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -59,9 +59,7 @@
 /**
  * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
  * this class and provide implementations of the RcsFeature methods that they support.
- * @hide
  */
-@SystemApi
 public class RcsFeature extends ImsFeature {
 
     private static final String LOG_TAG = "RcsFeature";
@@ -186,14 +184,14 @@
      * Contains the capabilities defined and supported by a {@link RcsFeature} in the
      * form of a bitmask. The capabilities that are used in the RcsFeature are
      * defined as:
-     * {@link RcsUceAdatper.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE}
-     * {@link RceUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE}
+     * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE}
+     * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE}
      *
      * The enabled capabilities of this RcsFeature will be set by the framework
-     * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}.
+     * using {#changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}.
      * After the capabilities have been set, the RcsFeature may then perform the necessary bring up
      * of the capability and notify the capability status as true using
-     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
+     * {#notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
      * framework that the capability is available for usage.
      */
     public static class RcsImsCapabilities extends Capabilities {
@@ -227,10 +225,18 @@
         public static final int CAPABILITY_TYPE_PRESENCE_UCE =  1 << 1;
 
         /**
+         * This is used to check the upper range of RCS capability
+         * {@hide}
+         */
+        public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_PRESENCE_UCE + 1;
+
+        /**
          * Create a new {@link RcsImsCapabilities} instance with the provided capabilities.
          * @param capabilities The capabilities that are supported for RCS in the form of a
          * bitfield.
+         * @hide
          */
+        @SystemApi
         public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) {
             super(capabilities);
         }
@@ -243,17 +249,29 @@
             super(capabilities.getMask());
         }
 
+        /**
+         * @hide
+         */
         @Override
+        @SystemApi
         public void addCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) {
             super.addCapabilities(capabilities);
         }
 
+        /**
+         * @hide
+         */
         @Override
+        @SystemApi
         public void removeCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) {
             super.removeCapabilities(capabilities);
         }
 
+        /**
+         * @hide
+         */
         @Override
+        @SystemApi
         public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) {
             return super.isCapable(capabilities);
         }
@@ -270,7 +288,9 @@
      * Method stubs called from the framework will be called asynchronously. To specify the
      * {@link Executor} that the methods stubs will be called, use
      * {@link RcsFeature#RcsFeature(Executor)} instead.
+     * @hide
      */
+    @SystemApi
     public RcsFeature() {
         super();
         // Run on the Binder threads that call them.
@@ -282,7 +302,9 @@
      * framework.
      * @param executor The executor for the framework to use when executing the methods overridden
      * by the implementation of RcsFeature.
+     * @hide
      */
+    @SystemApi
     public RcsFeature(@NonNull Executor executor) {
         super();
         if (executor == null) {
@@ -301,7 +323,7 @@
      * @hide
      */
     @Override
-    public void initialize(Context context, int slotId) {
+    public void initialize(@NonNull Context context, @NonNull int slotId) {
         super.initialize(context, slotId);
         // Notify that the RcsFeature is ready.
         mExecutor.execute(() -> onFeatureReady());
@@ -313,8 +335,10 @@
      * requests. To change the status of the capabilities
      * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called.
      * @return A copy of the current RcsFeature capability status.
+     * @hide
      */
     @Override
+    @SystemApi
     public @NonNull final RcsImsCapabilities queryCapabilityStatus() {
         return new RcsImsCapabilities(super.queryCapabilityStatus());
     }
@@ -324,7 +348,9 @@
      * this signals to the framework that the capability has been initialized and is ready.
      * Call {@link #queryCapabilityStatus()} to return the current capability status.
      * @param capabilities The current capability status of the RcsFeature.
+     * @hide
      */
+    @SystemApi
     public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) {
         if (capabilities == null) {
             throw new IllegalArgumentException("RcsImsCapabilities must be non-null!");
@@ -341,7 +367,9 @@
      * @param capability The capability that we are querying the configuration for.
      * @param radioTech The radio technology type that we are querying.
      * @return true if the capability is enabled, false otherwise.
+     * @hide
      */
+    @SystemApi
     public boolean queryCapabilityConfiguration(
             @RcsUceAdapter.RcsImsCapabilityFlag int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
@@ -364,8 +392,10 @@
      * be called for each capability change that resulted in an error.
      * @param request The request to change the capability.
      * @param callback To notify the framework that the result of the capability changes.
+     * @hide
      */
     @Override
+    @SystemApi
     public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
             @NonNull CapabilityCallbackProxy callback) {
         // Base Implementation - Override to provide functionality
@@ -385,7 +415,9 @@
      * event to the framework.
      * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability
      * exchange if it is supported by the device.
+     * @hide
      */
+    @SystemApi
     public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(
             @NonNull CapabilityExchangeEventListener listener) {
         // Base Implementation, override to implement functionality
@@ -395,20 +427,28 @@
     /**
      * Remove the given CapabilityExchangeImplBase instance.
      * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed.
+     * @hide
      */
+    @SystemApi
     public void destroyCapabilityExchangeImpl(
             @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) {
         // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance.
     }
 
-    /**{@inheritDoc}*/
+    /**{@inheritDoc}
+     * @hide
+     */
     @Override
+    @SystemApi
     public void onFeatureRemoved() {
 
     }
 
-    /**{@inheritDoc}*/
+    /**{@inheritDoc}
+     * @hide
+     */
     @Override
+    @SystemApi
     public void onFeatureReady() {
 
     }
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 3b151a4..ac5565b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -34,7 +34,6 @@
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.ArrayUtils;
 
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.CancellationException;
@@ -51,9 +50,7 @@
  * <p>
  * Note: There is no guarantee on the thread that the calls from the framework will be called on. It
  * is the implementors responsibility to handle moving the calls to a working thread if required.
- * @hide
  */
-@SystemApi
 public class ImsRegistrationImplBase {
 
     private static final String LOG_TAG = "ImsRegistrationImplBase";
@@ -94,6 +91,12 @@
      */
     public static final int REGISTRATION_TECH_NR = 3;
 
+    /**
+     * This is used to check the upper range of registration tech
+     * {@hide}
+     */
+    public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_NR + 1;
+
     // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
     // state.
     // The unknown state is set as the initialization state. This is so that we do not call back
@@ -109,7 +112,9 @@
      * Method stubs called from the framework will be called asynchronously. To specify the
      * {@link Executor} that the methods stubs will be called, use
      * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public ImsRegistrationImplBase() {
         super();
     }
@@ -119,7 +124,9 @@
      * framework.
      * @param executor The executor for the framework to use when executing the methods overridden
      * by the implementation of ImsRegistration.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public ImsRegistrationImplBase(@NonNull Executor executor) {
         super();
         mExecutor = executor;
@@ -250,7 +257,9 @@
      * If the SIP delegate feature tag configuration has changed, then this method will be
      * called in order to let the ImsService know that it can pick up these changes in the IMS
      * registration.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public void updateSipDelegateRegistration() {
         // Stub implementation, ImsService should implement this
     }
@@ -266,7 +275,9 @@
      * <p>
      * This should not affect the registration of features managed by the ImsService itself, such as
      * feature tags related to MMTEL registration.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public void triggerSipDelegateDeregistration() {
         // Stub implementation, ImsService should implement this
     }
@@ -284,7 +295,9 @@
      *    be carrier specific.
      * @param sipReason The reason associated with the SIP error code. {@code null} if there was no
      *    reason associated with the error.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode,
             @Nullable String sipReason) {
         // Stub implementation, ImsService should implement this
@@ -295,7 +308,9 @@
      * Notify the framework that the device is connected to the IMS network.
      *
      * @param imsRadioTech the radio access technology.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
         onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
     }
@@ -304,7 +319,9 @@
      * Notify the framework that the device is connected to the IMS network.
      *
      * @param attributes The attributes associated with the IMS registration.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         mCallbacks.broadcastAction((c) -> {
@@ -320,7 +337,9 @@
      * Notify the framework that the device is trying to connect the IMS network.
      *
      * @param imsRadioTech the radio access technology.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
         onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build());
     }
@@ -329,7 +348,9 @@
      * Notify the framework that the device is trying to connect the IMS network.
      *
      * @param attributes The attributes associated with the IMS registration.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) {
         updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         mCallbacks.broadcastAction((c) -> {
@@ -356,7 +377,9 @@
      * result.
      *
      * @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onDeregistered(ImsReasonInfo info) {
         updateToDisconnectedState(info);
         // ImsReasonInfo should never be null.
@@ -377,7 +400,9 @@
      * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and
      * {@link #REGISTRATION_TECH_CROSS_SIM}.
      * @param info The {@link ImsReasonInfo} for the failure to change technology.
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
             ImsReasonInfo info) {
         final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo();
@@ -396,7 +421,9 @@
      *
      * The {@link Uri}s are not guaranteed to be different between subsequent calls.
      * @param uris changed uris
+     * @hide This API is not part of the Android public SDK API
      */
+    @SystemApi
     public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
         synchronized (mLock) {
             mUris = ArrayUtils.cloneOrNull(uris);
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index eb59f87..81d5be8 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -242,8 +242,8 @@
 
     private DownloadRequest(Parcel in) {
         fileServiceId = in.readString();
-        sourceUri = in.readParcelable(getClass().getClassLoader());
-        destinationUri = in.readParcelable(getClass().getClassLoader());
+        sourceUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
+        destinationUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
         version = in.readInt();
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index e52b2ce..ffd864e 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -55,7 +55,7 @@
     }
 
     private FileInfo(Parcel in) {
-        uri = in.readParcelable(null);
+        uri = in.readParcelable(null, android.net.Uri.class);
         mimeType = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 8777e7f..0fc3be6 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -58,7 +58,7 @@
     FileServiceInfo(Parcel in) {
         super(in);
         files = new ArrayList<FileInfo>();
-        in.readList(files, FileInfo.class.getClassLoader());
+        in.readList(files, FileInfo.class.getClassLoader(), android.telephony.mbms.FileInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index f78e7a6..02424ff 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -80,7 +80,7 @@
         }
         names = new HashMap(mapCount);
         while (mapCount-- > 0) {
-            Locale locale = (java.util.Locale) in.readSerializable();
+            Locale locale = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
             String name = in.readString();
             names.put(locale, name);
         }
@@ -91,12 +91,12 @@
         }
         locales = new ArrayList<Locale>(localesCount);
         while (localesCount-- > 0) {
-            Locale l = (java.util.Locale) in.readSerializable();
+            Locale l = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
             locales.add(l);
         }
         serviceId = in.readString();
-        sessionStartTime = (java.util.Date) in.readSerializable();
-        sessionEndTime = (java.util.Date) in.readSerializable();
+        sessionStartTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
+        sessionEndTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
index 9258919..54d9d9e 100644
--- a/telephony/java/android/telephony/mbms/UriPathPair.java
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -48,8 +48,8 @@
 
     /** @hide */
     private UriPathPair(Parcel in) {
-        mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
-        mContentUri = in.readParcelable(Uri.class.getClassLoader());
+        mFilePathUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mContentUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final @android.annotation.NonNull Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index be54cec..bce7a24 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -55,6 +55,7 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.aidl.IFeatureProvisioningCallback;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
@@ -1992,6 +1993,18 @@
     void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback);
 
     /**
+     * Register an IMS provisioning change callback with Telephony.
+     */
+    void registerFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback);
+
+    /**
+     * unregister an existing IMS provisioning change callback.
+     */
+    void unregisterFeatureProvisioningChangedCallback(int subId,
+            IFeatureProvisioningCallback callback);
+
+    /**
      * Set the provisioning status for the IMS MmTel capability using the specified subscription.
      */
     void setImsProvisioningStatusForCapability(int subId, int capability, int tech,
@@ -2005,19 +2018,12 @@
     /**
      * Get the provisioning status for the IMS Rcs capability specified.
      */
-    boolean getRcsProvisioningStatusForCapability(int subId, int capability);
+    boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech);
 
     /**
      * Set the provisioning status for the IMS Rcs capability using the specified subscription.
      */
-    void setRcsProvisioningStatusForCapability(int subId, int capability,
-            boolean isProvisioned);
-
-    /** Is the capability and tech flagged as provisioned in the cache */
-    boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
-
-    /** Set the provisioning for the capability and tech in the cache */
-    void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech,
+    void setRcsProvisioningStatusForCapability(int subId, int capability, int tech,
             boolean isProvisioned);
 
     /**
@@ -2523,4 +2529,18 @@
      * @return the service name of the modem service which bind to.
      */
     String getModemService();
+
+    /**
+     * Is Provisioning required for capability
+     * @return true if provisioning is required for the MMTEL capability and IMS
+     * registration technology specified, false if it is not required.
+     */
+    boolean isProvisioningRequiredForCapability(int subId, int capability, int tech);
+
+    /**
+     * Is RCS Provisioning required for capability
+     * @return true if provisioning is required for the RCS capability and IMS
+     * registration technology specified, false if it is not required.
+     */
+    boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech);
 }
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
index d07d77c..8b49f4b 100644
--- a/telephony/java/com/android/internal/telephony/NetworkScanResult.java
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -83,7 +83,7 @@
         scanStatus = in.readInt();
         scanError = in.readInt();
         List<CellInfo> ni = new ArrayList<>();
-        in.readParcelableList(ni, Object.class.getClassLoader());
+        in.readParcelableList(ni, Object.class.getClassLoader(), android.telephony.CellInfo.class);
         networkInfos = ni;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
index a6f0f66..1820a1d 100644
--- a/telephony/java/com/android/internal/telephony/OperatorInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -189,7 +189,7 @@
                             in.readString(), /*operatorAlphaLong*/
                             in.readString(), /*operatorAlphaShort*/
                             in.readString(), /*operatorNumeric*/
-                            (State) in.readSerializable(), /*state*/
+                            (State) in.readSerializable(com.android.internal.telephony.OperatorInfo.State.class.getClassLoader(), com.android.internal.telephony.OperatorInfo.State.class), /*state*/
                             in.readInt()); /*ran*/
                     return opInfo;
                 }
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 21c3f76..f518d53 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -24,6 +24,7 @@
 import android.graphics.Color;
 import android.os.Build;
 import android.telephony.UiccPortInfo;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet;
@@ -934,6 +935,13 @@
     }
 
     /**
+     * Strip all the trailing 'F' characters of a string if exists and compare.
+     */
+    public static boolean compareIgnoreTrailingFs(String a, String b) {
+        return TextUtils.equals(a, b) || TextUtils.equals(stripTrailingFs(a), stripTrailingFs(b));
+    }
+
+    /**
      * Converts a character of [0-9a-fA-F] to its hex value in a byte. If the character is not a
      * hex number, 0 will be returned.
      */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 22acc03..d960e94 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -108,6 +108,15 @@
         super.entireScreenCovered()
     }
 
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        // This test doesn't work in shell transitions because of b/215885246
+        assumeFalse(isShellTransitionsEnabled)
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS
new file mode 100644
index 0000000..f7c0a87
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/OWNERS
@@ -0,0 +1,2 @@
+# window manager > animations/transitions
+# Bug component: 316275
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index 0b1748a..535612a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -17,7 +17,9 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.parser.toFlickerComponent
@@ -47,4 +49,28 @@
         }
         launcherStrategy.launch(appName, expectedPackage)
     }
+
+    fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
+        val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
+                "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
+
+        require(button != null) {
+            "Button not found, this usually happens when the device " +
+                    "was left in an unknown state (e.g. Screen turned off)"
+        }
+        button.click()
+        wmHelper.waitForAppTransitionIdle()
+        wmHelper.waitForFullScreenApp(
+                ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
+    }
+    fun dismissDialog(wmHelper: WindowManagerStateHelper) {
+        val dialog = uiDevice.wait(
+                Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
+
+        // Pressing back key to dismiss the dialog
+        if (dialog != null) {
+            uiDevice.pressBack()
+            wmHelper.waitForAppTransitionIdle()
+        }
+    }
 }
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/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
new file mode 100644
index 0000000..429541c
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -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 com.android.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
+ * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                eachRun {
+                    testApp.launchViaIntent(wmHelper)
+                    wmHelper.waitImeShown()
+                    testApp.startDialogThemedActivity(wmHelper)
+                }
+            }
+            teardown {
+                eachRun {
+                    testApp.exit()
+                }
+            }
+            transitions {
+                testApp.dismissDialog(wmHelper)
+            }
+        }
+    }
+
+    /**
+     * Checks that [FlickerComponentName.IME] layer becomes visible during the transition
+     */
+    @FlakyTest(bugId = 215884488)
+    @Test
+    fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+
+    /**
+     * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
+     */
+    @Presubmit
+    @Test
+    fun imeLayerExistsEnd() {
+        testSpec.assertLayersEnd {
+            this.isVisible(FlickerComponentName.IME)
+        }
+    }
+
+    /**
+     * Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always.
+     */
+    @Presubmit
+    @Test
+    fun imeSnapshotNotVisible() {
+        testSpec.assertLayers {
+            this.isInvisible(FlickerComponentName.IME_SNAPSHOT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+         * repetitions, screen orientation and navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests(
+                            repetitions = 3,
+                            supportedRotations = listOf(Surface.ROTATION_0),
+                            supportedNavigationModes = listOf(
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                            )
+                    )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS
new file mode 100644
index 0000000..301fafa
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OWNERS
@@ -0,0 +1,2 @@
+# ime
+# Bug component: 34867
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 0ad0a03..5e06f11 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -18,8 +18,8 @@
 
 import android.app.Instrumentation
 import android.platform.test.annotations.Presubmit
+import android.view.Display
 import android.view.Surface
-import android.view.WindowManagerPolicyConstants
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
@@ -41,7 +41,9 @@
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.traces.common.ConditionList
 import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.FixMethodOrder
@@ -63,6 +65,12 @@
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
+    private val waitConditionSetup = ConditionList(listOf(
+        WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
+        WindowManagerConditionsFactory.hasLayersAnimating().negate(),
+        WindowManagerConditionsFactory.isHomeActivityVisible()
+    ))
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
@@ -73,8 +81,7 @@
                 }
                 eachRun {
                     device.pressRecentApps()
-                    wmHelper.waitImeGone()
-                    wmHelper.waitForAppTransitionIdle()
+                    wmHelper.waitFor(waitConditionSetup)
                     this.setRotation(testSpec.startRotation)
                 }
             }
@@ -231,11 +238,8 @@
         fun getParams(): Collection<FlickerTestParameter> {
             return FlickerTestParameterFactory.getInstance()
                 .getConfigNonRotationTests(
-                    repetitions = 1,
-                    supportedRotations = listOf(Surface.ROTATION_0),
-                    supportedNavigationModes = listOf(
-                        WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
-                    )
+                    repetitions = 5,
+                    supportedRotations = listOf(Surface.ROTATION_0)
                 )
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS
new file mode 100644
index 0000000..2c414a2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OWNERS
@@ -0,0 +1,4 @@
+# System UI > ... > Overview (recent apps) > UI
+# Bug template url: https://b.corp.google.com/issues/new?component=807991&template=1390280 = per-file *Overview*
+# window manager > animations/transitions
+# Bug template url: https://b.corp.google.com/issues/new?component=316275&template=1018192
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 e07a8f9..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
@@ -122,6 +116,11 @@
     @Test
     override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
 
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 213852103)
+    @Test
+    override fun entireScreenCovered() = super.entireScreenCovered()
+
     companion object {
         /**
          * Creates the test configurations.
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 6e5c600..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
@@ -127,7 +125,7 @@
      * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging,
      * the window may be visible or not depending on what was processed until that moment.
      */
-    @Presubmit
+    @FlakyTest(bugId = 203538234)
     @Test
     fun appWindowBecomesVisible() {
         testSpec.assertWm {
@@ -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!")
@@ -240,7 +232,7 @@
      * it cannot use the regular assertion (check over time), because on lock screen neither
      * the app not the launcher are visible, and there is no top visible window.
      */
-    @Presubmit
+    @FlakyTest(bugId = 203538234)
     @Test
     override fun appWindowReplacesLauncherAsTopWindow() {
         testSpec.assertWm {
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
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS
new file mode 100644
index 0000000..897fe5d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/OWNERS
@@ -0,0 +1,2 @@
+# System UI > ... > Launcher > Gesture nav
+# Bug component: 565144
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS
new file mode 100644
index 0000000..f7c0a87
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/OWNERS
@@ -0,0 +1,2 @@
+# window manager > animations/transitions
+# Bug component: 316275
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 0a88f6b..9e371e5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -97,5 +97,16 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".DialogThemedActivity"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
+            android:configChanges="orientation|screenSize"
+            android:theme="@style/DialogTheme"
+            android:label="DialogThemedActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index 2620ff4..baaf707 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -31,4 +31,9 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="Finish activity" />
+    <Button
+        android:id="@+id/start_dialog_themed_activity_btn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Start dialog themed activity" />
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 87a61a8..746b0f4c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -27,4 +27,15 @@
     <style name="CutoutNever">
         <item name="android:windowLayoutInDisplayCutoutMode">never</item>
     </style>
-</resources>
\ No newline at end of file
+
+    <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@null</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowSoftInputMode">stateUnchanged</item>
+    </style>
+</resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index baf36ab..13adb68 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -56,4 +56,8 @@
     public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
             new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+    public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
+    public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
+            new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".DialogThemedActivity");
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java
new file mode 100644
index 0000000..27606d8
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java
@@ -0,0 +1,57 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class DialogThemedActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_simple);
+        getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT);
+        TextView textView = new TextView(this);
+        textView.setText("This is a test dialog");
+        textView.setTextColor(Color.BLACK);
+        LinearLayout layout = new LinearLayout(this);
+        layout.setBackgroundColor(Color.GREEN);
+        layout.addView(textView);
+
+        // Create a dialog with dialog-themed activity
+        AlertDialog dialog = new AlertDialog.Builder(this)
+                .setView(layout)
+                .setTitle("Dialog for test")
+                .create();
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(MATCH_PARENT,
+                MATCH_PARENT);
+        attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM;
+        dialog.getWindow().getDecorView().setLayoutParams(attrs);
+        dialog.setCanceledOnTouchOutside(true);
+        dialog.show();
+        dialog.setOnDismissListener((d) -> finish());
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index 05da717..bb200f1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import android.content.Intent;
+import android.widget.Button;
 import android.widget.EditText;
 
 public class ImeActivityAutoFocus extends ImeActivity {
@@ -26,5 +28,9 @@
 
         EditText editTextField = findViewById(R.id.plain_text_input);
         editTextField.requestFocus();
+
+        Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
+        startThemedActivityButton.setOnClickListener(
+                button -> startActivity(new Intent(this, DialogThemedActivity.class)));
     }
 }
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 3131f56..99f77fe 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -54,6 +54,7 @@
     'or': 'Orya',
     'pa': 'Guru',
     'pt': 'Latn',
+    'ru': 'Latn',
     'sk': 'Latn',
     'sl': 'Latn',
     'sq': 'Latn',
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 900c214..a6fd9bb 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -31,7 +31,9 @@
             CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
             CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
             CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY,
-            CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED
+            CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
     )
 
     override val api: Int
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
new file mode 100644
index 0000000..8011b36
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.detector.api.AnnotationInfo
+import com.android.tools.lint.detector.api.AnnotationOrigin
+import com.android.tools.lint.detector.api.AnnotationUsageInfo
+import com.android.tools.lint.detector.api.AnnotationUsageType
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UElement
+
+/**
+ * Lint Detector that ensures that any method overriding a method annotated
+ * with @EnforcePermission is also annotated with the exact same annotation.
+ * The intent is to surface the effective permission checks to the service
+ * implementations.
+ */
+class EnforcePermissionDetector : Detector(), SourceCodeScanner {
+
+    val ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
+
+    override fun applicableAnnotations(): List<String> {
+        return listOf(ENFORCE_PERMISSION)
+    }
+
+    private fun areAnnotationsEquivalent(
+        context: JavaContext,
+        anno1: PsiAnnotation,
+        anno2: PsiAnnotation
+    ): Boolean {
+        if (anno1.qualifiedName != anno2.qualifiedName) {
+            return false
+        }
+        val attr1 = anno1.parameterList.attributes
+        val attr2 = anno2.parameterList.attributes
+        if (attr1.size != attr2.size) {
+            return false
+        }
+        for (i in attr1.indices) {
+            if (attr1[i].name != attr2[i].name) {
+                return false
+            }
+            val v1 = ConstantEvaluator.evaluate(context, attr1[i].value)
+            val v2 = ConstantEvaluator.evaluate(context, attr2[i].value)
+            if (v1 != v2) {
+                return false
+            }
+        }
+        return true
+    }
+
+    override fun visitAnnotationUsage(
+        context: JavaContext,
+        element: UElement,
+        annotationInfo: AnnotationInfo,
+        usageInfo: AnnotationUsageInfo
+    ) {
+        if (usageInfo.type == AnnotationUsageType.EXTENDS) {
+            val newClass = element.sourcePsi?.parent?.parent as PsiClass
+            val extendedClass: PsiClass = usageInfo.referenced as PsiClass
+            val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION)
+            val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)!!
+
+            val location = context.getLocation(element)
+            val newClassName = newClass.qualifiedName
+            val extendedClassName = extendedClass.qualifiedName
+            if (newAnnotation == null) {
+                val msg = "The class $newClassName extends the class $extendedClassName which " +
+                    "is annotated with @EnforcePermission. The same annotation must be used " +
+                    "on $newClassName."
+                context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
+            } else if (!areAnnotationsEquivalent(context, newAnnotation, extendedAnnotation)) {
+                val msg = "The class $newClassName is annotated with ${newAnnotation.text} " +
+                    "which differs from the parent class $extendedClassName: " +
+                    "${extendedAnnotation.text}. The same annotation must be used for " +
+                    "both classes."
+                context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
+            }
+        } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
+            annotationInfo.origin == AnnotationOrigin.METHOD) {
+            val overridingMethod = element.sourcePsi as PsiMethod
+            val overriddenMethod = usageInfo.referenced as PsiMethod
+            val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION)
+            val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)!!
+
+            val location = context.getLocation(element)
+            val overridingClass = overridingMethod.parent as PsiClass
+            val overriddenClass = overriddenMethod.parent as PsiClass
+            val overridingName = "${overridingClass.name}.${overridingMethod.name}"
+            val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}"
+            if (overridingAnnotation == null) {
+                val msg = "The method $overridingName overrides the method $overriddenName which " +
+                    "is annotated with @EnforcePermission. The same annotation must be used " +
+                    "on $overridingName"
+                context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
+            } else if (!areAnnotationsEquivalent(
+                        context, overridingAnnotation, overriddenAnnotation)) {
+                val msg = "The method $overridingName is annotated with " +
+                    "${overridingAnnotation.text} which differs from the overridden " +
+                    "method $overriddenName: ${overriddenAnnotation.text}. The same " +
+                    "annotation must be used for both methods."
+                context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
+            }
+        }
+    }
+
+    companion object {
+        val EXPLANATION = """
+            The @EnforcePermission annotation is used to indicate that the underlying binder code
+            has already verified the caller's permissions before calling the appropriate method. The
+            verification code is usually generated by the AIDL compiler, which also takes care of
+            annotating the generated Java code.
+
+            In order to surface that information to platform developers, the same annotation must be
+            used on the implementation class or methods.
+            """
+
+        val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MissingEnforcePermissionAnnotation",
+            briefDescription = "Missing @EnforcePermission annotation on Binder method",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    Scope.JAVA_FILE_SCOPE
+            )
+        )
+
+        val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MismatchingEnforcePermissionAnnotation",
+            briefDescription = "Incorrect @EnforcePermission annotation on Binder method",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
new file mode 100644
index 0000000..f5f4ebe
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.google.android.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
+        lint().files(java(
+            """
+            package test.pkg;
+            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+            public class TestClass1 extends IFoo.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass2 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDetectIssuesMismatchingAnnotationOnClass() {
+        lint().files(java(
+            """
+            package test.pkg;
+            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+            public class TestClass3 extends IFoo.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
+annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+which differs from the parent class IFoo.Stub: \
+@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
+same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
+public class TestClass3 extends IFoo.Stub {
+                                ~~~~~~~~~
+1 errors, 0 warnings""".addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass4 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
+annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+which differs from the overridden method Stub.testMethod: \
+@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
+annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+    public void testMethod() {}
+                ~~~~~~~~~~
+1 errors, 0 warnings""".addLineContinuation())
+    }
+
+    fun testDetectIssuesMissingAnnotationOnClass() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass5 extends IFoo.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
+the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
+used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
+public class TestClass5 extends IFoo.Stub {
+                                ~~~~~~~~~
+1 errors, 0 warnings""".addLineContinuation())
+    }
+
+    fun testDetectIssuesMissingAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass6 extends IFooMethod.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
+overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
+annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
+    public void testMethod() {}
+                ~~~~~~~~~~
+1 errors, 0 warnings""".addLineContinuation())
+    }
+
+    /* Stubs */
+
+    private val interfaceIFooStub: TestFile = java(
+        """
+        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+        public interface IFoo {
+         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+         public static abstract class Stub implements IFoo {
+           @Override
+           public void testMethod() {}
+         }
+         public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val interfaceIFooMethodStub: TestFile = java(
+        """
+        public interface IFooMethod {
+         public static abstract class Stub implements IFooMethod {
+            @Override
+            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+            public void testMethod() {}
+          }
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub,
+            manifestPermissionStub, enforcePermissionAnnotationStub)
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 3b75660..459696e 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -1262,6 +1262,26 @@
     }
 
     /**
+     * Notifies the wificond daemon that the WiFi framework has successfully updated the Country
+     * Code of the driver. The wificond daemon needs this notification if the device does not
+     * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond
+     * updates in internal state in response to this Country Code update.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean notifyCountryCodeChanged() {
+        try {
+            if (mWificond != null) {
+                mWificond.notifyCountryCodeChanged();
+                return true;
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to notify country code changed due to remote exception");
+        }
+        return false;
+    }
+
+    /**
      * Register the provided callback handler for SoftAp events. The interface must first be created
      * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
      * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 3fb2301..4032a7b 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -1137,6 +1137,26 @@
         assertEquals(capaExpected, capaActual);
     }
 
+    /**
+     * Tests notifyCountryCodeChanged
+     */
+    @Test
+    public void testNotifyCountryCodeChanged() throws Exception {
+        doNothing().when(mWificond).notifyCountryCodeChanged();
+        assertTrue(mWificondControl.notifyCountryCodeChanged());
+        verify(mWificond).notifyCountryCodeChanged();
+    }
+
+    /**
+     * Tests notifyCountryCodeChanged with RemoteException
+     */
+    @Test
+    public void testNotifyCountryCodeChangedRemoteException() throws Exception {
+        doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged();
+        assertFalse(mWificondControl.notifyCountryCodeChanged());
+        verify(mWificond).notifyCountryCodeChanged();
+    }
+
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {